@yuneta/gobj-ui 7.23.171 → 7.23.173

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/src/yui_dev.js CHANGED
@@ -13,6 +13,13 @@ import {
13
13
  kw_get_local_storage_value,
14
14
  kw_set_local_storage_value,
15
15
  gobj_write_attr,
16
+ gobj_command,
17
+ gobj_global_trace_level,
18
+ gobj_global_trace_no_level,
19
+ gobj_get_gclass_trace_level2,
20
+ gclass_find_by_name,
21
+ trace_level_t,
22
+ log_error,
16
23
  gobj_create_service,
17
24
  gobj_find_service,
18
25
  gobj_start,
@@ -21,16 +28,6 @@ import {
21
28
  gobj_set_trace_machine_format,
22
29
  } from "@yuneta/gobj-js";
23
30
 
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
-
34
31
  import i18next, {t} from 'i18next';
35
32
 
36
33
  /***********************************************************************
@@ -41,11 +38,8 @@ import i18next, {t} from 'i18next';
41
38
  * losing history. Reopening the window repaints the buffer.
42
39
  ***********************************************************************/
43
40
  const TRAFFIC_MAX = 600; // capped history
44
- const PERIODIC_THRESHOLD = 5; // a signature seen >= N times reads as recurring
45
- const PERIODIC_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING/i;
46
41
 
47
- let TRAFFIC_LOG = []; // [{title,event,command,sig,dir,size,ts,kw,jn,hay,$node}]
48
- let TRAFFIC_COUNTS = new Map(); // signature -> occurrences (for periodic detection)
42
+ let TRAFFIC_LOG = []; // [{title,event,command,dir,size,ts,kw,jn,hay,$node}]
49
43
  let SEARCH_TEXT = ""; // session-only free-text filter (not persisted)
50
44
 
51
45
  /* Running totals of framework errors/warnings seen since page load (or the
@@ -61,8 +55,18 @@ const TRAFFIC_TS_FIELDS = {
61
55
  "from_t": 1, "to_t": 1, "from_tm": 1, "to_tm": 1, "time": 1,
62
56
  };
63
57
 
64
- /* Trace toggles: [localStorage key, display label, handler]. */
65
- /* `data-label` carries the i18n KEY and not the text: the chips are
58
+ /* Trace toggles.
59
+ *
60
+ * A chip does ONE thing: it turns trace bits of the runtime on or off
61
+ * through the yuno's trace commands -- the C kernel's, same names -- and
62
+ * the yuno persists them (`trace_levels` / `no_trace_levels`). Whether a
63
+ * message is traced is decided by the gobj that emits it, never by this
64
+ * window reading messages. The chip's state is READ from the runtime, so
65
+ * it cannot disagree with what is traced.
66
+ *
67
+ * [key, label i18n key, title i18n key, state(), toggle()]
68
+ *
69
+ * `data-label` carries the i18n KEY and not the text: the chips are
66
70
  * repainted from it by `refresh_dev_chrome()` on every toggle, so a
67
71
  * label translated once at build time would come back in English at
68
72
  * the first click. */
@@ -70,36 +74,48 @@ const TRAFFIC_TS_FIELDS = {
70
74
  * `validate-locales`: the table below and the `grp` / `mk_view` /
71
75
  * `mk_expand` / `mk_dir` / `mk_out` helpers pass their key as a
72
76
  * VARIABLE, and `OUT_TITLES` holds three more in a lookup table --
73
- * so they, so a scan of `t("…")`
74
- * sees none of them and reports OK with the whole window in English --
75
- * which is how it shipped in gui_agent and gui_treedb. The full set a
76
- * consumer must define, kept here so it can be copied:
77
+ * so a scan of `t("…")` sees none of them and reports OK with the
78
+ * whole window in English. The full set a consumer must define,
79
+ * kept here so it can be copied:
77
80
  *
78
81
  * automata, both, browser console only, clear,
79
82
  * clear captured traffic, compact, console, copied, copy,
80
83
  * copy visible traffic to clipboard, creation, data, detailed,
81
84
  * dev window and browser console, dev window only, developer,
82
- * errors, expand, expanded, filter events / payload, find,
83
- * hide the timers and the recurring traffic, i18n, incoming, log,
84
- * machine trace shape, metadata, mute this message, muted,
85
- * name only, no poll, outgoing, output, periodic, schema, show,
85
+ * errors, expand, expanded, filter events / payload, find, i18n,
86
+ * i18next debug output, incoming, log, machine trace shape,
87
+ * metadata, name only, outgoing, output, periodic, schema, show,
86
88
  * show this section in the expanded view, simple mach,
87
- * start / stop, subscriptions, traces, traffic, unmute, view,
88
- * window, yuno monitor
89
+ * start / stop, subscriptions,
90
+ * trace every event of every automaton,
91
+ * trace subscriptions and publications,
92
+ * trace the creation and destruction of gobjs,
93
+ * trace the messages to and from the backend,
94
+ * trace the periodic timer event,
95
+ * trace the start and stop of gobjs,
96
+ * traces, traffic, view, window, yuno monitor
89
97
  *
90
98
  * wattyzer and the yunovatios GUIs carry them; check a new consumer
91
99
  * against this list, and confirm it by DUMPING the window. */
92
100
  const TRACE_DEFS = [
93
- ["trace_automata", "automata", trace_automata],
94
- ["trace_creation", "creation", trace_creation],
95
- ["trace_start_stop", "start / stop", trace_start_stop],
96
- ["trace_subscriptions", "subscriptions", trace_subscriptions],
97
- ["trace_i18n", "i18n", trace_i18n],
98
- ["trace_traffic", "traffic", trace_traffic],
99
- ["no_poll", "no poll", set_no_poll],
101
+ ["automata", "automata", "trace every event of every automaton",
102
+ automata_state, toggle_automata],
103
+ ["creation", "creation", "trace the creation and destruction of gobjs",
104
+ () => global_bit(trace_level_t.TRACE_CREATE_DELETE), () => toggle_global("create_delete")],
105
+ ["start_stop", "start / stop", "trace the start and stop of gobjs",
106
+ () => global_bit(trace_level_t.TRACE_START_STOP), () => toggle_global("start_stop")],
107
+ ["subscriptions", "subscriptions", "trace subscriptions and publications",
108
+ () => global_bit(trace_level_t.TRACE_SUBSCRIPTIONS), () => toggle_global("subscriptions")],
109
+ ["i18n", "i18n", "i18next debug output",
110
+ i18n_state, toggle_i18n],
111
+ ["traffic", "traffic", "trace the messages to and from the backend",
112
+ traffic_state, toggle_traffic],
113
+ ["periodic", "periodic", "trace the periodic timer event",
114
+ periodic_state, toggle_periodic],
100
115
  ];
101
116
 
102
117
 
118
+
103
119
  /******************************
104
120
  * Small helpers
105
121
  ******************************/
@@ -201,17 +217,6 @@ function dev_view()
201
217
  return (v === "compact" || v === "name" || v === "full") ? v : "detailed";
202
218
  }
203
219
 
204
- /* ON by default. The machine trace is the reason this window
205
- * exists, and a yuno with one timer emits two lines a second for
206
- * ever: leaving the filter off meant the first thing anybody saw
207
- * after enabling Automata was a wall of EV_TIMEOUT with their own
208
- * transitions somewhere inside it. The chip turns it back off.
209
- *
210
- * A CONSTANT, and not the literal in two places: the reader and the
211
- * toggle must agree on what "unset" means, or the first click on the
212
- * chip writes the value it already had and appears to do nothing. */
213
- const HIDE_PERIODIC_DEFAULT = 1;
214
-
215
220
  /* Which SHAPE the machine trace is written in — the two the C kernel
216
221
  * offers, and the chip that swaps them:
217
222
  *
@@ -220,7 +225,7 @@ const HIDE_PERIODIC_DEFAULT = 1;
220
225
  * change, and the `<- … ret: N` return
221
226
  *
222
227
  * 1 because that is what the C kernel defaults to, so a browser yuno and
223
- * a node read alike. Like HIDE_PERIODIC_DEFAULT, a CONSTANT and not a
228
+ * a node read alike. A CONSTANT and not a
224
229
  * literal in each of the three places that read it (the chip, the chrome
225
230
  * and start up): they have to agree on what "unset" means, or the chip
226
231
  * is born showing the wrong state and its first click does nothing. */
@@ -241,11 +246,6 @@ function dev_simple_mach()
241
246
  return dev_num(SIMPLE_MACH_KEY, SIMPLE_MACH_DEFAULT) ? 1 : 0;
242
247
  }
243
248
 
244
- function dev_hide_periodic()
245
- {
246
- return dev_num("dev_hide_periodic", HIDE_PERIODIC_DEFAULT) ? true : false;
247
- }
248
-
249
249
  /* Where dev output (traffic + all logs + automata) is routed:
250
250
  * "window" → dev window only (browser console silenced framework-wide)
251
251
  * "console" → browser console only (nothing appended to the window)
@@ -259,46 +259,12 @@ function dev_route()
259
259
  }
260
260
 
261
261
  /* Push the current route's console decision down to gobj-js. Called from
262
- * apply_dev_traces (startup), whenever the Output selector changes, and
263
- * whenever a filter that the console also obeys is toggled. */
262
+ * apply_dev_traces (startup) and whenever the Output selector changes. */
264
263
  function apply_console_route()
265
264
  {
266
265
  set_console_log_enabled(dev_route() !== "window");
267
-
268
- /* ONE rule, TWO sinks.
269
- *
270
- * The window's Periodic filter used to end at the window, so with the
271
- * Output on "Both" the timers were gone from the panel and still
272
- * arriving in the browser console, two lines a second — the same flood,
273
- * one pane over. It cannot be fixed from this side alone: gobj-js writes
274
- * the console line BEFORE it calls the log sink, so nothing here can
275
- * un-print it. It hands out a say instead (`set_console_log_filter`,
276
- * gobj-js >= 7.13.6), and this is the same predicate `entry_hidden()`
277
- * uses, so the two panes cannot disagree about what is noise.
278
- *
279
- * Feature-detected rather than required: against an older gobj-js the
280
- * window still filters and the console still floods, exactly as before,
281
- * and the reason is logged once instead of failing. */
282
- if(typeof gobj_js.set_console_log_filter === "function") {
283
- gobj_js.set_console_log_filter((level, msg) => {
284
- if(!dev_hide_periodic()) {
285
- return true;
286
- }
287
- return !log_is_periodic(level, is_string(msg) ? msg : String(msg));
288
- });
289
- } else if(!__console_filter_warned__) {
290
- __console_filter_warned__ = true;
291
- console.warn(
292
- "yui_dev: gobj-js has no set_console_log_filter (needs >= 7.13.6): " +
293
- "the Periodic filter applies to this window only, not to the console"
294
- );
295
- }
296
266
  }
297
267
 
298
- /* The warning above is worth saying, and worth saying ONCE: this runs on
299
- * every Output change and every filter toggle. */
300
- let __console_filter_warned__ = false;
301
-
302
268
  /* Emit one inter-event traffic line to the browser console (routes
303
269
  * "console" and "both"). gobj-js only knows about framework logs, not this
304
270
  * traffic feed, so the console mirror is produced here. */
@@ -315,20 +281,6 @@ function console_traffic(title, jn, direction, size)
315
281
  );
316
282
  }
317
283
 
318
- function dev_muted()
319
- {
320
- let a = kw_get_local_storage_value("dev_muted_events", [], false);
321
- if(!Array.isArray(a)) {
322
- a = [];
323
- }
324
- return new Set(a);
325
- }
326
-
327
- function dev_set_muted(set)
328
- {
329
- kw_set_local_storage_value("dev_muted_events", Array.from(set));
330
- }
331
-
332
284
  function set_view(v)
333
285
  {
334
286
  kw_set_local_storage_value("dev_view_mode", v);
@@ -351,64 +303,21 @@ function toggle_pref(key, def)
351
303
  {
352
304
  let v = dev_num(key, def) ? 0 : 1;
353
305
  kw_set_local_storage_value(key, v);
354
- /* The console filter closes over dev_hide_periodic(), which reads the
355
- * stored value on each call, so it follows this on its own. Re-pushed
356
- * anyway: a filter installed before this module reached apply_dev_traces
357
- * is the difference between the console obeying the chip and ignoring
358
- * it, and one assignment is cheaper than that class of bug. */
359
- apply_console_route();
360
306
  rerender_all();
361
307
  refresh_dev_chrome();
362
308
  }
363
309
 
364
- function mute_signature(sig)
365
- {
366
- let set = dev_muted();
367
- set.add(sig);
368
- dev_set_muted(set);
369
- rerender_all();
370
- refresh_dev_chrome();
371
- }
372
-
373
- function unmute_signature(sig)
374
- {
375
- let set = dev_muted();
376
- set.delete(sig);
377
- dev_set_muted(set);
378
- rerender_all();
379
- refresh_dev_chrome();
380
- }
381
-
382
-
383
310
  /******************************
384
311
  * Filtering
385
312
  ******************************/
386
313
 
387
314
 
388
- /* Signature identifies a "kind" of message. Command answers share
389
- * the generic EV_MT_COMMAND event, so fold the command in to tell
390
- * a get-stats poll apart from a user action. */
391
- function traffic_signature(event, command)
392
- {
393
- return command ? (event + " · " + command) : event;
394
- }
395
-
396
- function traffic_is_periodic(sig)
397
- {
398
- if(PERIODIC_RE.test(sig)) {
399
- return true;
400
- }
401
- return (TRAFFIC_COUNTS.get(sig) || 0) >= PERIODIC_THRESHOLD;
402
- }
403
-
404
315
  function build_filter_ctx()
405
316
  {
406
317
  return {
407
318
  out: dev_num("dev_filter_out", 1),
408
319
  inc: dev_num("dev_filter_in", 1),
409
320
  err: dev_num("dev_filter_err", 1),
410
- muted: dev_muted(),
411
- hide_periodic: dev_hide_periodic(),
412
321
  search: SEARCH_TEXT,
413
322
  };
414
323
  }
@@ -418,20 +327,9 @@ function entry_hidden(e, ctx)
418
327
  if(e.kind === "log") {
419
328
  /* Mirrored console logs respect the search box only — not the
420
329
  * in/out/err traffic filters, which are about DIRECTION and a
421
- * log has none.
422
- *
423
- * The PERIODIC filter does reach them, and it has to: once
424
- * Automata is on, the machine trace is what floods this
425
- * window, and every one of those lines arrives here as a log.
426
- * It applies to a machine TRANSITION only, and never above
427
- * debug level — see log_is_periodic(). By NAME and not by
428
- * count, unlike the traffic half below: a busy FSM crosses
429
- * the recurrence threshold on almost every event within
430
- * seconds, so counting here would empty the window of the
431
- * very transitions somebody turned Automata on to read. */
432
- if(ctx.hide_periodic && e.periodic) {
433
- return true;
434
- }
330
+ * log has none. What is traced at all is the runtime's trace
331
+ * levels, set by the chips: this window never reads a message
332
+ * to decide whether to show it. */
435
333
  return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
436
334
  }
437
335
  if(e.dir === 1 && !ctx.out) {
@@ -443,12 +341,6 @@ function entry_hidden(e, ctx)
443
341
  if(e.dir === 3 && !ctx.err) {
444
342
  return true;
445
343
  }
446
- if(ctx.muted.has(e.sig)) {
447
- return true;
448
- }
449
- if(ctx.hide_periodic && traffic_is_periodic(e.sig)) {
450
- return true;
451
- }
452
344
  if(ctx.search && e.hay.indexOf(ctx.search) < 0) {
453
345
  return true;
454
346
  }
@@ -481,11 +373,6 @@ function ensure_dev_style()
481
373
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
482
374
  }
483
375
  .YDEV_LOG { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 6px 10px; }
484
- .YDEV_MUTED {
485
- display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
486
- padding: 4px 10px; border-bottom: 1px solid rgba(0,0,0,0.08); font-size: 12px;
487
- }
488
- .YDEV_MUTED:empty { display: none; }
489
376
  .YDEV_STATS {
490
377
  flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 14px;
491
378
  padding: 6px 10px; border-top: 1px solid rgba(0,0,0,0.1);
@@ -517,7 +404,6 @@ function ensure_dev_style()
517
404
  .YDEV_CHIP.s-in.is-active { background: rgba(5,150,105,0.16); border-color: #059669; color: #059669; }
518
405
  .YDEV_CHIP.s-err.is-active { background: rgba(220,38,38,0.16); border-color: #dc2626; color: #dc2626; }
519
406
  .YDEV_CHIP[data-dir]:not(.is-active) { opacity: 0.4; text-decoration: line-through; }
520
- .YDEV_CHIP[data-toggle="periodic"].is-active { background: rgba(217,119,6,0.16); border-color: #d97706; color: #b45309; font-weight: 600; }
521
407
  .YDEV_SEG { display: inline-flex; border: 1px solid rgba(0,0,0,0.18); border-radius: 7px; overflow: hidden; }
522
408
  .YDEV_SEG_BTN {
523
409
  font: inherit; font-size: 12px; padding: 4px 10px; border: 0;
@@ -531,12 +417,6 @@ function ensure_dev_style()
531
417
  border: 1px solid rgba(0,0,0,0.18); border-radius: 7px;
532
418
  background: transparent; color: inherit;
533
419
  }
534
- .YDEV_MUTED_CHIP {
535
- font: inherit; font-family: "DejaVu Sans Mono", monospace; font-size: 12px;
536
- display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px;
537
- border: 1px solid rgba(217,119,6,0.5); border-radius: 999px;
538
- background: rgba(217,119,6,0.12); color: #b45309; cursor: pointer;
539
- }
540
420
  .YDEV_STAT.s-out { color: #2563eb; } .YDEV_STAT.s-in { color: #059669; } .YDEV_STAT.s-err { color: #dc2626; }
541
421
  .YDEV_STAT.s-warn { color: #d97706; }
542
422
  .YDEV_STAT.is-strong { font-weight: 700; }
@@ -564,10 +444,7 @@ function ensure_dev_style()
564
444
  .dir-err .TRAFFIC_ARROW, .dir-err .TRAFFIC_EVENT { color: #dc2626; }
565
445
  .TRAFFIC_SUMMARY { opacity: 0.6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
566
446
  .TRAFFIC_META { margin-left: auto; opacity: 0.6; font-size: 11px; white-space: nowrap; }
567
- .TRAFFIC_MUTE { border: 0; background: transparent; color: inherit; cursor: pointer; opacity: 0; font-size: 12px; padding: 0 2px; flex: 0 0 auto; }
568
- .TRAFFIC_ENTRY:hover .TRAFFIC_MUTE, .TRAFFIC_LINE:hover .TRAFFIC_MUTE, .TRAFFIC_NAME:hover .TRAFFIC_MUTE { opacity: 0.5; }
569
- .TRAFFIC_MUTE:hover { opacity: 1 !important; color: #d97706; }
570
- .TRAFFIC_KW { margin: 2px 0 0 16px; }
447
+ .TRAFFIC_ENTRY:hover .TRAFFIC_KW { margin: 2px 0 0 16px; }
571
448
  .TRAFFIC_FULL { margin: 4px 0 0 16px; padding: 6px 8px; font-family: monospace; font-size: 11px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; background: rgba(0,0,0,0.04); border-radius: 4px; overflow-x: auto; }
572
449
  .TRAFFIC_ROW { display: flex; gap: 6px; align-items: baseline; }
573
450
  .TRAFFIC_BULLET { opacity: 0.45; flex: 0 0 auto; }
@@ -611,9 +488,7 @@ details.TRAFFIC_NEST > summary::-webkit-details-marker { display: none; }
611
488
  :root[data-theme="dark"] .TRAFFIC_VAL.t-bool, :root[data-theme="dark"] .TRAFFIC_VAL.t-null { color: #c084fc; }
612
489
  :root[data-theme="dark"] .YDEV_CHIP.is-active { background: rgba(96,165,250,0.2); border-color: #60a5fa; color: #93c5fd; }
613
490
  :root[data-theme="dark"] .YDEV_SEG_BTN.is-active { background: #2563eb; color: #fff; }
614
- :root[data-theme="dark"] .YDEV_CHIP[data-toggle="periodic"].is-active { background: rgba(217,119,6,0.24); border-color: #f59e0b; color: #fbbf24; }
615
- :root[data-theme="dark"] .YDEV_MUTED_CHIP { border-color: rgba(245,158,11,0.5); background: rgba(245,158,11,0.16); color: #fbbf24; }
616
- `;
491
+ :root[data-theme="dark"] `;
617
492
  let $style = document.createElement('style');
618
493
  $style.id = 'yui-dev-style';
619
494
  $style.textContent = css;
@@ -727,21 +602,6 @@ function traffic_bullets(obj)
727
602
  ******************************/
728
603
 
729
604
 
730
- /* A small mute affordance that silences this signature (persistent). */
731
- function mute_button(sig)
732
- {
733
- /* `t('mute this message')` and not `'Mute ' + sig`: a composed
734
- * title is no i18n key, and the row already says which message. */
735
- return ['button', {class: 'TRAFFIC_MUTE', type: 'button',
736
- title: t('mute this message'), 'data-i18n-title': 'mute this message'}, '⊘', {
737
- click: (ev) => {
738
- ev.stopPropagation();
739
- ev.preventDefault();
740
- mute_signature(sig);
741
- }
742
- }];
743
- }
744
-
745
605
  function event_spans(e)
746
606
  {
747
607
  let spans = [['span', {class: 'TRAFFIC_ARROW'}, dir_arrow(e.dir)],
@@ -786,7 +646,6 @@ function compact_summary(kw)
786
646
  function render_detailed(e)
787
647
  {
788
648
  let head = event_spans(e);
789
- head.push(mute_button(e.sig));
790
649
  head.push(['span', {class: 'TRAFFIC_META'}, `${traffic_size(e.size)} · ${e.ts}`]);
791
650
 
792
651
  let children = [['div', {class: 'TRAFFIC_HEADER'}, head]];
@@ -843,7 +702,6 @@ function full_sections(payload)
843
702
  function render_full(e)
844
703
  {
845
704
  let head = event_spans(e);
846
- head.push(mute_button(e.sig));
847
705
  head.push(['span', {class: 'TRAFFIC_META'}, `${traffic_size(e.size)} · ${e.ts}`]);
848
706
 
849
707
  let payload = full_sections(e.kw ? e.kw : e.jn);
@@ -864,7 +722,6 @@ function render_compact(e)
864
722
  {
865
723
  let kids = event_spans(e);
866
724
  kids.push(['span', {class: 'TRAFFIC_SUMMARY'}, compact_summary(e.kw)]);
867
- kids.push(mute_button(e.sig));
868
725
  kids.push(['span', {class: 'TRAFFIC_META'}, `${traffic_size(e.size)} · ${e.ts}`]);
869
726
  return createElement2(['div', {class: 'TRAFFIC_LINE ' + dir_class(e.dir), title: e.title}, kids]);
870
727
  }
@@ -872,7 +729,6 @@ function render_compact(e)
872
729
  function render_name(e)
873
730
  {
874
731
  let kids = event_spans(e);
875
- kids.push(mute_button(e.sig));
876
732
  kids.push(['span', {class: 'TRAFFIC_META'}, e.ts]);
877
733
  return createElement2(['div', {class: 'TRAFFIC_NAME ' + dir_class(e.dir), title: e.title}, kids]);
878
734
  }
@@ -932,7 +788,6 @@ function render_entry(e)
932
788
  function clear_traffic()
933
789
  {
934
790
  TRAFFIC_LOG.length = 0;
935
- TRAFFIC_COUNTS.clear();
936
791
  LOG_ERR_COUNT = 0;
937
792
  LOG_WARN_COUNT = 0;
938
793
  let logger = document.getElementById('developer-traffic-logger');
@@ -1116,7 +971,6 @@ function info_traffic(title, msg, direction, size)
1116
971
  let event = (jn && jn.event) ? String(jn.event) : "(no event)";
1117
972
  let kw = (jn && jn.kw && typeof jn.kw === "object") ? jn.kw : null;
1118
973
  let command = (kw && typeof kw.command === "string") ? kw.command : "";
1119
- let sig = traffic_signature(event, command);
1120
974
 
1121
975
  let hay = "";
1122
976
  try {
@@ -1126,35 +980,21 @@ function info_traffic(title, msg, direction, size)
1126
980
  }
1127
981
 
1128
982
  let entry = {
1129
- title: title || "", event: event, command: command, sig: sig,
983
+ title: title || "", event: event, command: command,
1130
984
  dir: direction, size: size, ts: traffic_now(),
1131
985
  kw: kw, jn: jn, hay: hay, $node: null,
1132
986
  };
1133
987
 
1134
988
  TRAFFIC_LOG.push(entry);
1135
- TRAFFIC_COUNTS.set(sig, (TRAFFIC_COUNTS.get(sig) || 0) + 1);
1136
-
1137
- /* When a signature just crosses the "recurring" threshold and
1138
- * the periodic filter is on, its earlier entries must disappear
1139
- * too — a full repaint is the correct, simple answer. */
1140
- let crossed = dev_hide_periodic() && (TRAFFIC_COUNTS.get(sig) === PERIODIC_THRESHOLD);
1141
989
 
1142
990
  if(TRAFFIC_LOG.length > TRAFFIC_MAX) {
1143
991
  let old = TRAFFIC_LOG.shift();
1144
- let c = (TRAFFIC_COUNTS.get(old.sig) || 0) - 1;
1145
- if(c <= 0) {
1146
- TRAFFIC_COUNTS.delete(old.sig);
1147
- } else {
1148
- TRAFFIC_COUNTS.set(old.sig, c);
1149
- }
1150
992
  if(old.$node && old.$node.parentNode) {
1151
993
  old.$node.parentNode.removeChild(old.$node);
1152
994
  }
1153
995
  }
1154
996
 
1155
- if(crossed) {
1156
- rerender_all();
1157
- } else if(!entry_hidden(entry, build_filter_ctx())) {
997
+ if(!entry_hidden(entry, build_filter_ctx())) {
1158
998
  let node = render_entry(entry);
1159
999
  entry.$node = node;
1160
1000
  /* Drop the "no traffic yet" placeholder before the first row. */
@@ -1221,28 +1061,14 @@ function info_log(level, msg, hora)
1221
1061
  } else {
1222
1062
  text = is_string(msg) ? msg : String(msg);
1223
1063
  }
1224
- /* A machine line is signed by its EVENT, not by "log:debug":
1225
- * that is what makes a hundred EV_TIMEOUTs ONE recurring
1226
- * thing the filter and the counter can see, instead of a
1227
- * hundred anonymous debug lines they cannot tell apart. */
1228
1064
  let entry = {
1229
1065
  kind: "log", level: lvl, text: text,
1230
1066
  dir: 0, size: 0, ts: traffic_now(),
1231
- sig: log_signature(lvl, text),
1232
- periodic: log_is_periodic(lvl, text),
1233
1067
  hay: (lvl + " " + text).toLowerCase(), $node: null,
1234
1068
  };
1235
1069
  TRAFFIC_LOG.push(entry);
1236
1070
  if(TRAFFIC_LOG.length > TRAFFIC_MAX) {
1237
1071
  let old = TRAFFIC_LOG.shift();
1238
- if(old.kind !== "log") {
1239
- let c = (TRAFFIC_COUNTS.get(old.sig) || 0) - 1;
1240
- if(c <= 0) {
1241
- TRAFFIC_COUNTS.delete(old.sig);
1242
- } else {
1243
- TRAFFIC_COUNTS.set(old.sig, c);
1244
- }
1245
- }
1246
1072
  if(old.$node && old.$node.parentNode) {
1247
1073
  old.$node.parentNode.removeChild(old.$node);
1248
1074
  }
@@ -1270,78 +1096,108 @@ function info_log(level, msg, hora)
1270
1096
  ******************************/
1271
1097
 
1272
1098
 
1273
- function trace_traffic()
1099
+ /* Send one of the yuno's trace commands; a refusal is said. */
1100
+ function yuno_trace_command(command, kw)
1274
1101
  {
1275
- let v = Number(kw_get_local_storage_value("trace_traffic"));
1276
- if(v) {
1277
- gobj_write_attr(gobj_yuno(), "trace_inter_event", false);
1278
- v = 0;
1279
- } else {
1280
- gobj_write_attr(gobj_yuno(), "trace_inter_event", true);
1281
- gobj_write_attr(gobj_yuno(), "trace_ievent_callback", info_traffic);
1282
- v = 1;
1102
+ let r = gobj_command(gobj_yuno(), command, kw, gobj_yuno());
1103
+ if(!r || r.result < 0) {
1104
+ log_error(`yui_dev: ${command} ${JSON.stringify(kw)}: ${r ? r.comment : "no answer"}`);
1283
1105
  }
1284
- kw_set_local_storage_value("trace_traffic", v);
1285
1106
  refresh_dev_chrome();
1286
1107
  }
1287
1108
 
1288
- function trace_automata()
1109
+ function global_bit(bit)
1110
+ {
1111
+ return (gobj_global_trace_level() & bit) ? 1 : 0;
1112
+ }
1113
+
1114
+ const GLOBAL_TRACE_BITS = {
1115
+ "machine": trace_level_t.TRACE_MACHINE,
1116
+ "ev_kw": trace_level_t.TRACE_EV_KW,
1117
+ "create_delete": trace_level_t.TRACE_CREATE_DELETE,
1118
+ "start_stop": trace_level_t.TRACE_START_STOP,
1119
+ "subscriptions": trace_level_t.TRACE_SUBSCRIPTIONS,
1120
+ };
1121
+
1122
+ function toggle_global(level)
1123
+ {
1124
+ yuno_trace_command("set-global-trace",
1125
+ {level: level, set: global_bit(GLOBAL_TRACE_BITS[level]) ? 0 : 1});
1126
+ }
1127
+
1128
+ /* Automata cycles 0 → 1 (machine) → 2 (machine + the kw of each event) → 0. */
1129
+ function automata_state()
1289
1130
  {
1290
- let v = Number(kw_get_local_storage_value("trace_automata"));
1131
+ if(!global_bit(trace_level_t.TRACE_MACHINE)) {
1132
+ return 0;
1133
+ }
1134
+ return global_bit(trace_level_t.TRACE_EV_KW) ? 2 : 1;
1135
+ }
1136
+
1137
+ function toggle_automata()
1138
+ {
1139
+ let v = automata_state();
1291
1140
  if(v === 0) {
1292
- v = 1;
1141
+ yuno_trace_command("set-global-trace", {level: "machine", set: 1});
1293
1142
  } else if(v === 1) {
1294
- v = 2;
1143
+ yuno_trace_command("set-global-trace", {level: "ev_kw", set: 1});
1295
1144
  } else {
1296
- v = 0;
1145
+ yuno_trace_command("set-global-trace", {level: "ev_kw", set: 0});
1146
+ yuno_trace_command("set-global-trace", {level: "machine", set: 0});
1297
1147
  }
1298
- gobj_write_attr(gobj_yuno(), "tracing", v);
1299
- kw_set_local_storage_value("trace_automata", v);
1300
- refresh_dev_chrome();
1301
1148
  }
1302
1149
 
1303
- function trace_creation()
1150
+ /* Traffic is C_IEVENT_CLI's own level, as `set-gclass-trace
1151
+ * gclass=C_IEVENT_CLI level=ievents` on a node. */
1152
+ function traffic_state()
1304
1153
  {
1305
- let v = Number(kw_get_local_storage_value("trace_creation"));
1306
- v = v === 0 ? 1 : 0;
1307
- gobj_write_attr(gobj_yuno(), "trace_creation", v);
1308
- kw_set_local_storage_value("trace_creation", v);
1309
- refresh_dev_chrome();
1154
+ /* An app with no websocket registers no C_IEVENT_CLI: nothing to trace,
1155
+ * and nothing wrong with that, so ask before reading its levels. */
1156
+ if(!gclass_find_by_name("C_IEVENT_CLI")) {
1157
+ return 0;
1158
+ }
1159
+ return gobj_get_gclass_trace_level2("C_IEVENT_CLI").includes("ievents") ? 1 : 0;
1310
1160
  }
1311
1161
 
1312
- function trace_start_stop()
1162
+ function toggle_traffic()
1313
1163
  {
1314
- let v = Number(kw_get_local_storage_value("trace_start_stop"));
1315
- v = v === 0 ? 1 : 0;
1316
- gobj_write_attr(gobj_yuno(), "trace_start_stop", v);
1317
- kw_set_local_storage_value("trace_start_stop", v);
1318
- refresh_dev_chrome();
1164
+ if(!gclass_find_by_name("C_IEVENT_CLI")) {
1165
+ log_error("yui_dev: no C_IEVENT_CLI in this app, there is no traffic to trace");
1166
+ return;
1167
+ }
1168
+ yuno_trace_command("set-gclass-trace",
1169
+ {gclass_name: "C_IEVENT_CLI", level: "ievents", set: traffic_state() ? 0 : 1});
1319
1170
  }
1320
1171
 
1321
- function trace_subscriptions()
1172
+ /* Periodic is EV_TIMEOUT_PERIODIC and nothing else: the global level
1173
+ * `timer_periodic`. main() silences it with the NO-trace of the same
1174
+ * level, which wins over the trace, so the chip moves both. */
1175
+ function periodic_state()
1322
1176
  {
1323
- let v = Number(kw_get_local_storage_value("trace_subscriptions"));
1324
- v = v === 0 ? 1 : 0;
1325
- gobj_write_attr(gobj_yuno(), "trace_subscriptions", v);
1326
- kw_set_local_storage_value("trace_subscriptions", v);
1327
- refresh_dev_chrome();
1177
+ let on = gobj_global_trace_level() & trace_level_t.TRACE_TIMER_PERIODIC;
1178
+ let off = gobj_global_trace_no_level() & trace_level_t.TRACE_TIMER_PERIODIC;
1179
+ return (on && !off) ? 1 : 0;
1328
1180
  }
1329
1181
 
1330
- function trace_i18n()
1182
+ function toggle_periodic()
1331
1183
  {
1332
- let v = Number(kw_get_local_storage_value("trace_i18n"));
1333
- v = v === 0 ? 1 : 0;
1334
- i18next.options.debug = v ? true : false;
1335
- kw_set_local_storage_value("trace_i18n", v);
1336
- refresh_dev_chrome();
1184
+ let v = periodic_state() ? 0 : 1;
1185
+ yuno_trace_command("set-global-trace", {level: "timer_periodic", set: v});
1186
+ yuno_trace_command("set-global-no-trace", {level: "timer_periodic", set: v ? 0 : 1});
1337
1187
  }
1338
1188
 
1339
- function set_no_poll()
1189
+ /* I18n is no trace of the runtime: i18next's own debug switch, kept in
1190
+ * the browser as it always was. */
1191
+ function i18n_state()
1340
1192
  {
1341
- let v = Number(kw_get_local_storage_value("no_poll"));
1342
- v = v ? 0 : 1;
1343
- gobj_write_attr(gobj_yuno(), "no_poll", v);
1344
- kw_set_local_storage_value("no_poll", v);
1193
+ return Number(kw_get_local_storage_value("trace_i18n", 0, false)) ? 1 : 0;
1194
+ }
1195
+
1196
+ function toggle_i18n()
1197
+ {
1198
+ let v = i18n_state() ? 0 : 1;
1199
+ i18next.options.debug = v ? true : false;
1200
+ kw_set_local_storage_value("trace_i18n", v);
1345
1201
  refresh_dev_chrome();
1346
1202
  }
1347
1203
 
@@ -1351,16 +1207,17 @@ function set_no_poll()
1351
1207
  ******************************/
1352
1208
 
1353
1209
 
1354
- /* Sync every control's visual state from persisted prefs, plus the
1355
- * muted-events row and the stats strip. Idempotent; null-guarded so
1210
+ /* Sync every control's visual state -- the trace chips from the
1211
+ * runtime, the rest from persisted prefs -- plus the stats strip. Idempotent; null-guarded so
1356
1212
  * it is safe to call whether or not the window is mounted. */
1357
1213
  function refresh_dev_chrome()
1358
1214
  {
1359
1215
  document.querySelectorAll('.YDEV_CHIP[data-trace]').forEach(($b) => {
1360
1216
  let key = $b.getAttribute('data-trace');
1217
+ let def = TRACE_DEFS.find((d) => d[0] === key);
1361
1218
  let label = t($b.getAttribute('data-label') || '');
1362
- let v = dev_num(key, 0);
1363
- $b.textContent = (key === "trace_automata" && v > 0) ? (label + " " + v) : label;
1219
+ let v = def ? def[3]() : 0;
1220
+ $b.textContent = (key === "automata" && v > 0) ? (label + " " + v) : label;
1364
1221
  $b.classList.toggle('is-active', v > 0);
1365
1222
  });
1366
1223
 
@@ -1388,39 +1245,10 @@ function refresh_dev_chrome()
1388
1245
  $b.classList.toggle('is-active', !!dev_num($b.getAttribute('data-dir'), 1));
1389
1246
  });
1390
1247
 
1391
- document.querySelectorAll('.YDEV_CHIP[data-toggle="periodic"]').forEach(($b) => {
1392
- $b.classList.toggle('is-active', dev_hide_periodic());
1393
- });
1394
-
1395
1248
  document.querySelectorAll('.YDEV_CHIP[data-toggle="automata-simple"]').forEach(($b) => {
1396
1249
  $b.classList.toggle('is-active', !!dev_simple_mach());
1397
1250
  });
1398
1251
 
1399
- let $m = document.getElementById('ydev-muted');
1400
- if($m) {
1401
- $m.replaceChildren();
1402
- let set = dev_muted();
1403
- if(set.size) {
1404
- let $lbl = document.createElement('span');
1405
- $lbl.className = 'YDEV_LABEL';
1406
- $lbl.textContent = t('muted');
1407
- $lbl.setAttribute('data-i18n', 'muted');
1408
- $m.appendChild($lbl);
1409
- set.forEach((sig) => {
1410
- $m.appendChild(createElement2(
1411
- ['button', {class: 'YDEV_MUTED_CHIP', type: 'button',
1412
- title: t('unmute'), 'data-i18n-title': 'unmute'},
1413
- '⊘ ' + sig + ' ✕', {
1414
- click: (ev) => {
1415
- ev.stopPropagation();
1416
- unmute_signature(sig);
1417
- }
1418
- }]
1419
- ));
1420
- });
1421
- }
1422
- }
1423
-
1424
1252
  update_stats();
1425
1253
  }
1426
1254
 
@@ -1485,17 +1313,19 @@ function dev_fallback_copy(text)
1485
1313
  document.body.removeChild(ta);
1486
1314
  }
1487
1315
 
1488
- /* The control bar: trace toggles, view selector, direction /
1489
- * periodic filters, free-text search, copy, clear. Returns an element. */
1316
+ /* The control bar: trace toggles, view selector, direction
1317
+ * filters, free-text search, copy, clear. Returns an element. */
1490
1318
  function build_control_bar()
1491
1319
  {
1492
- let trace_chips = TRACE_DEFS.map(([key, label_key, fn]) => ['button', {
1320
+ let trace_chips = TRACE_DEFS.map(([key, label_key, title_key, state, toggle]) => ['button', {
1493
1321
  class: 'YDEV_CHIP', 'data-trace': key, 'data-label': label_key,
1494
1322
  'data-i18n': label_key, type: 'button',
1323
+ title: t(title_key), 'data-i18n-title': title_key,
1324
+ 'aria-label': t(label_key), 'data-i18n-aria-label': label_key,
1495
1325
  }, t(label_key), {
1496
1326
  click: (ev) => {
1497
1327
  ev.stopPropagation();
1498
- fn();
1328
+ toggle();
1499
1329
  }
1500
1330
  }]);
1501
1331
 
@@ -1594,24 +1424,6 @@ function build_control_bar()
1594
1424
  mk_dir('err', '⚠', 'dev_filter_err', 'errors'),
1595
1425
  ];
1596
1426
 
1597
- let periodic_chip = ['button', {
1598
- class: 'YDEV_CHIP', 'data-toggle': 'periodic', type: 'button',
1599
- title: t('hide the timers and the recurring traffic'),
1600
- 'data-i18n-title': 'hide the timers and the recurring traffic',
1601
- }, [
1602
- /* The glyph is its own element so the label beside it can carry
1603
- * its key: `refresh_language()` replaces the FIRST text node of
1604
- * the element that carries `data-i18n`, and that would have
1605
- * eaten the glyph with the word. */
1606
- ['span', {class: 'YDEV_GLYPH'}, '⊘'],
1607
- ['span', {'data-i18n': 'periodic'}, t('periodic')]
1608
- ], {
1609
- click: (ev) => {
1610
- ev.stopPropagation();
1611
- toggle_pref('dev_hide_periodic', HIDE_PERIODIC_DEFAULT);
1612
- }
1613
- }];
1614
-
1615
1427
  let search = ['input', {
1616
1428
  class: 'YDEV_SEARCH', type: 'search', 'data-role': 'search',
1617
1429
  placeholder: t('filter events / payload'),
@@ -1656,7 +1468,7 @@ function build_control_bar()
1656
1468
  grp('traces', [...trace_chips, simple_mach]), sep(),
1657
1469
  grp('output', [output_seg]), sep(),
1658
1470
  grp('view', [view_seg]), expand_grp, sep(),
1659
- grp('show', [...dir_chips, periodic_chip]), sep(),
1471
+ grp('show', dir_chips), sep(),
1660
1472
  grp('find', [search]), sep(),
1661
1473
  grp('log', [copy, clear]),
1662
1474
  ]]);
@@ -1672,12 +1484,11 @@ function build_title_header()
1672
1484
  ]]);
1673
1485
  }
1674
1486
 
1675
- /* The monitor body: control bar + muted row + log + stats strip. */
1487
+ /* The monitor body: control bar + log + stats strip. */
1676
1488
  function build_dev_body()
1677
1489
  {
1678
1490
  return createElement2(['div', {class: 'YDEV_BODY'}, [
1679
1491
  build_control_bar(),
1680
- ['div', {class: 'YDEV_MUTED', id: 'ydev-muted'}, []],
1681
1492
  ['div', {class: 'YDEV_LOG', id: 'developer-traffic-logger'}, []],
1682
1493
  ['div', {class: 'YDEV_STATS', id: 'ydev-stats'}, []],
1683
1494
  ]]);
@@ -1702,32 +1513,19 @@ function dev_window_was_open()
1702
1513
  }
1703
1514
 
1704
1515
  /************************************************************
1705
- * Apply ALL persisted developer-trace flags to the running
1706
- * yuno. Independent of the dev window — call it once at app
1707
- * startup so a refresh keeps logging whatever was enabled.
1516
+ * Wire the developer window's sinks and the browser-side prefs.
1517
+ * Call it once at app startup.
1518
+ *
1519
+ * The TRACE LEVELS are not here: the yuno restores the ones the user
1520
+ * persisted in its own mt_create (`trace_levels` / `no_trace_levels`),
1521
+ * as a C yuno does. What is left is where the output goes.
1708
1522
  ************************************************************/
1709
1523
  function apply_dev_traces()
1710
1524
  {
1711
- let traffic = Number(kw_get_local_storage_value("trace_traffic", 0, false));
1712
- let trace = Number(kw_get_local_storage_value("trace_automata", 0, false));
1713
- let creation = Number(kw_get_local_storage_value("trace_creation", 0, false));
1714
- let start_stop = Number(kw_get_local_storage_value("trace_start_stop", 0, false));
1715
- let subscriptions = Number(kw_get_local_storage_value("trace_subscriptions", 0, false));
1716
- let i18n = Number(kw_get_local_storage_value("trace_i18n", 0, false));
1717
- let no_poll = Number(kw_get_local_storage_value("no_poll", 0, false));
1718
-
1719
- if(traffic) {
1720
- gobj_write_attr(gobj_yuno(), "trace_inter_event", true);
1721
- gobj_write_attr(gobj_yuno(), "trace_ievent_callback", info_traffic);
1722
- } else {
1723
- gobj_write_attr(gobj_yuno(), "trace_inter_event", false);
1724
- }
1725
- gobj_write_attr(gobj_yuno(), "tracing", trace);
1726
- gobj_write_attr(gobj_yuno(), "trace_creation", creation);
1727
- gobj_write_attr(gobj_yuno(), "trace_start_stop", start_stop);
1728
- gobj_write_attr(gobj_yuno(), "trace_subscriptions", subscriptions);
1729
- gobj_write_attr(gobj_yuno(), "no_poll", no_poll);
1730
- i18next.options.debug = i18n ? true : false;
1525
+ /* The traffic trace of C_IEVENT_CLI lands in this window. */
1526
+ gobj_write_attr(gobj_yuno(), "trace_ievent_callback", info_traffic);
1527
+
1528
+ i18next.options.debug = i18n_state() ? true : false;
1731
1529
 
1732
1530
  /* Which shape the machine trace is written in (persisted). */
1733
1531
  gobj_set_trace_machine_format(dev_simple_mach());
@@ -1812,8 +1610,8 @@ function setup_dev(self, show)
1812
1610
  *
1813
1611
  * Returns { $el, dispose }:
1814
1612
  * - $el: the panel element (control bar + log + stats).
1815
- * - dispose: stops the inter-event traffic trace; call it from
1816
- * the modal's on_close.
1613
+ * - dispose: call it from the modal's on_close (kept for the
1614
+ * contract; the traces outlive the panel).
1817
1615
  ************************************************************/
1818
1616
  function build_dev_panel()
1819
1617
  {
@@ -1844,8 +1642,9 @@ function build_dev_panel()
1844
1642
  rerender_all();
1845
1643
  }, 0);
1846
1644
 
1645
+ /* Nothing to undo: a trace level is the yuno's state, persisted,
1646
+ * and it is not tied to this panel being open. */
1847
1647
  let dispose = function() {
1848
- gobj_write_attr(gobj_yuno(), "trace_inter_event", false);
1849
1648
  };
1850
1649
 
1851
1650
  return {$el: $el, dispose: dispose};