@yuneta/gobj-ui 7.23.170 → 7.23.172

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
32
  }) : target, mod));
33
33
  //#endregion
34
34
  let _yuneta_gobj_js = require("@yuneta/gobj-js");
35
- _yuneta_gobj_js = __toESM(_yuneta_gobj_js, 1);
36
35
  let i18next = require("i18next");
37
36
  i18next = __toESM(i18next, 1);
38
37
  let tom_select = require("tom-select");
@@ -23051,13 +23050,455 @@ function nodes_answer(data) {
23051
23050
  };
23052
23051
  }
23053
23052
  //#endregion
23053
+ //#region src/schema_model.js
23054
+ /***********************************************************************
23055
+ * schema_model.js
23056
+ *
23057
+ * A SCHEMA out of the three meta-topics that store it.
23058
+ *
23059
+ * `treedb_system_schema` keeps every schema of a yuno as DATA, in
23060
+ * three flat topics — `treedbs`, `topics`, `cols` — linked by
23061
+ * fkeys. That is the right STORAGE and the wrong thing to put in
23062
+ * front of an operator: adding one column to one topic means
23063
+ * finding it in a table holding every column of every topic of
23064
+ * every treedb the yuno has.
23065
+ *
23066
+ * This module turns those three lists back into what they
23067
+ * describe: treedb -> topics -> columns, each in its declared
23068
+ * `order`. Pure, so the editor's model can be tested with no DOM
23069
+ * and no backend — the gclass fetches and draws, this decides
23070
+ * what the records MEAN.
23071
+ *
23072
+ * TWO THINGS IT EXISTS TO STATE ONCE:
23073
+ *
23074
+ * 1. A record is grouped by its FKEY, not by its id. The id is
23075
+ * qualified (`<treedb>.<topic>.<column>`) and reading it back
23076
+ * with a split on '.' works right up to the first treedb or
23077
+ * topic whose name carries one. The fkey is the link the store
23078
+ * itself uses; the id is only the fallback for a record whose
23079
+ * fkey never arrived.
23080
+ *
23081
+ * 2. A fkey reaches this code in FOUR shapes, because `nodes`
23082
+ * answers differently depending on the options it was called
23083
+ * with: the bare ref string, the expanded object, a list of
23084
+ * either, or a dict keyed by the ref. All four are the same
23085
+ * fact and parse_fkey_ref() flattens them to it.
23086
+ *
23087
+ * Copyright (c) 2026, ArtGins.
23088
+ * All Rights Reserved.
23089
+ ***********************************************************************/
23090
+ var DEFAULT_ORDER = 9999;
23091
+ /***************************************************************
23092
+ * parse_fkey_ref(value) -> [{topic_name, id, hook_name}, ...]
23093
+ *
23094
+ * Every shape a fkey column can arrive in, flattened to the
23095
+ * refs it names. Anything unrecognizable contributes nothing —
23096
+ * a schema drawn without one link beats a screen that throws.
23097
+ ***************************************************************/
23098
+ function parse_fkey_ref(value) {
23099
+ let refs = [];
23100
+ let push_one = (v) => {
23101
+ if (typeof v === "string") {
23102
+ let parts = v.split("^");
23103
+ if (parts.length !== 3) return;
23104
+ refs.push({
23105
+ topic_name: parts[0],
23106
+ id: parts[1],
23107
+ hook_name: parts[2]
23108
+ });
23109
+ return;
23110
+ }
23111
+ if (v && typeof v === "object") {
23112
+ let id = v.id;
23113
+ if (typeof id !== "string" || id.length === 0) return;
23114
+ refs.push({
23115
+ topic_name: v.topic_name || "",
23116
+ id,
23117
+ hook_name: v.hook_name || ""
23118
+ });
23119
+ }
23120
+ };
23121
+ if (value === null || value === void 0) return refs;
23122
+ if (typeof value === "string") {
23123
+ push_one(value);
23124
+ return refs;
23125
+ }
23126
+ if (Array.isArray(value)) {
23127
+ for (let v of value) push_one(v);
23128
+ return refs;
23129
+ }
23130
+ if (typeof value === "object") for (let [key, v] of Object.entries(value)) if (v && typeof v === "object" && typeof v.id === "string") push_one(v);
23131
+ else push_one(key);
23132
+ return refs;
23133
+ }
23134
+ /***************************************************************
23135
+ * The id of the FIRST parent a record names, or "" when it
23136
+ * names none. A record has one parent here — a column belongs to
23137
+ * one topic, a topic to one treedb — and a second ref would be a
23138
+ * store this editor cannot draw anyway.
23139
+ ***************************************************************/
23140
+ function parent_id(record, fkey_col) {
23141
+ let refs = parse_fkey_ref(record ? record[fkey_col] : null);
23142
+ return refs.length > 0 ? refs[0].id : "";
23143
+ }
23144
+ /***************************************************************
23145
+ * The name a record is KNOWN by, which is not what it is keyed
23146
+ * by: `topics` and `cols` key by the qualified id and carry the
23147
+ * bare name in `value` (its pkey2). An older store that still
23148
+ * keyed by rowid has no `value` worth showing, so the id stands
23149
+ * in — the same fallback as node_label().
23150
+ ***************************************************************/
23151
+ function record_name(record) {
23152
+ if (!record) return "";
23153
+ if (typeof record.value === "string" && record.value.length > 0) return record.value;
23154
+ return typeof record.id === "string" ? record.id : "";
23155
+ }
23156
+ /***************************************************************
23157
+ * The `order` of a record as a NUMBER, whatever the store put
23158
+ * there: an integer column read back from JSON may arrive as a
23159
+ * string, and comparing "10" with 9 sorts the schema wrong.
23160
+ ***************************************************************/
23161
+ function record_order(record) {
23162
+ let order = record ? record.order : void 0;
23163
+ if (typeof order === "number" && isFinite(order)) return order;
23164
+ if (typeof order === "string" && order.trim().length > 0) {
23165
+ let n = Number(order);
23166
+ if (isFinite(n)) return n;
23167
+ }
23168
+ return DEFAULT_ORDER;
23169
+ }
23170
+ /***************************************************************
23171
+ * Sort in place by `order`, ties broken by name so a schema
23172
+ * whose records all default to 9999 still draws the same way
23173
+ * twice.
23174
+ ***************************************************************/
23175
+ function sort_by_order(list) {
23176
+ list.sort((a, b) => {
23177
+ if (a.order !== b.order) return a.order - b.order;
23178
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
23179
+ });
23180
+ return list;
23181
+ }
23182
+ /***************************************************************
23183
+ * build_schema_model({treedbs, topics, cols}) -> model
23184
+ *
23185
+ * model = {
23186
+ * treedbs: [{
23187
+ * id, name, record,
23188
+ * schema_version, c_schema_version, system_schema_version,
23189
+ * topics: [{
23190
+ * id, name, order, record,
23191
+ * pkey, topic_version, system_topic, main_topic,
23192
+ * cols: [{id, name, order, record}]
23193
+ * }]
23194
+ * }],
23195
+ * orphan_topics: [...], topics whose treedb is not in the store
23196
+ * orphan_cols: [...] columns whose topic is not in the store
23197
+ * }
23198
+ *
23199
+ * An orphan is NOT dropped. A column whose topic was deleted is
23200
+ * exactly the kind of leftover an operator opens this editor to
23201
+ * find, and a model that hides it makes the editor lie.
23202
+ ***************************************************************/
23203
+ function build_schema_model(records) {
23204
+ let r = records || {};
23205
+ let treedb_rows = Array.isArray(r.treedbs) ? r.treedbs : [];
23206
+ let topic_rows = Array.isArray(r.topics) ? r.topics : [];
23207
+ let col_rows = Array.isArray(r.cols) ? r.cols : [];
23208
+ let treedbs = [];
23209
+ let by_treedb_id = {};
23210
+ for (let record of treedb_rows) {
23211
+ if (!record || typeof record.id !== "string") continue;
23212
+ let entry = {
23213
+ id: record.id,
23214
+ name: record.id,
23215
+ record,
23216
+ schema_version: record.schema_version,
23217
+ c_schema_version: record.c_schema_version,
23218
+ system_schema_version: record.system_schema_version,
23219
+ topics: []
23220
+ };
23221
+ treedbs.push(entry);
23222
+ by_treedb_id[entry.id] = entry;
23223
+ }
23224
+ let orphan_topics = [];
23225
+ let by_topic_id = {};
23226
+ for (let record of topic_rows) {
23227
+ if (!record || typeof record.id !== "string") continue;
23228
+ let entry = {
23229
+ id: record.id,
23230
+ name: record_name(record),
23231
+ order: record_order(record),
23232
+ record,
23233
+ treedb_id: parent_id(record, "treedbs"),
23234
+ pkey: record.pkey || "",
23235
+ pkey2s: record.pkey2s,
23236
+ system_flag: record.system_flag || "",
23237
+ tkey: record.tkey || "",
23238
+ topic_version: record.topic_version,
23239
+ system_topic: !!record.system_topic,
23240
+ main_topic: !!record.main_topic,
23241
+ cols: []
23242
+ };
23243
+ by_topic_id[entry.id] = entry;
23244
+ let parent = by_treedb_id[entry.treedb_id];
23245
+ if (parent) parent.topics.push(entry);
23246
+ else orphan_topics.push(entry);
23247
+ }
23248
+ let orphan_cols = [];
23249
+ for (let record of col_rows) {
23250
+ if (!record || typeof record.id !== "string") continue;
23251
+ let entry = {
23252
+ id: record.id,
23253
+ name: record_name(record),
23254
+ order: record_order(record),
23255
+ record,
23256
+ topic_id: parent_id(record, "topics")
23257
+ };
23258
+ let parent = by_topic_id[entry.topic_id];
23259
+ if (parent) parent.cols.push(entry);
23260
+ else orphan_cols.push(entry);
23261
+ }
23262
+ for (let treedb of treedbs) {
23263
+ sort_by_order(treedb.topics);
23264
+ for (let topic of treedb.topics) sort_by_order(topic.cols);
23265
+ }
23266
+ for (let topic of orphan_topics) sort_by_order(topic.cols);
23267
+ treedbs.sort((a, b) => {
23268
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
23269
+ });
23270
+ sort_by_order(orphan_topics);
23271
+ sort_by_order(orphan_cols);
23272
+ return {
23273
+ treedbs,
23274
+ orphan_topics,
23275
+ orphan_cols
23276
+ };
23277
+ }
23278
+ /***************************************************************
23279
+ * Is there a value here at all?
23280
+ *
23281
+ * The store answers a `blob` column that was never set with an
23282
+ * EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
23283
+ * `default: {}`. Read as "a value", those turn every export into
23284
+ * a schema full of empty objects — and `'hook': {}` in a literal
23285
+ * is a hook with no mapping, which is a link the treedb builds
23286
+ * and nothing writes.
23287
+ ***************************************************************/
23288
+ function is_empty_value(value) {
23289
+ if (value === null || value === void 0 || value === "") return true;
23290
+ if (Array.isArray(value)) return value.length === 0;
23291
+ if (typeof value === "object") return Object.keys(value).length === 0;
23292
+ return false;
23293
+ }
23294
+ /***************************************************************
23295
+ * The fields of a column that are DECLARED as one thing and
23296
+ * STORED as another.
23297
+ *
23298
+ * `hook`, `enum`, `default`, `pkey2s` and `properties` are `blob`
23299
+ * columns of the `cols` topic (treedb_system_schema.c), so what
23300
+ * comes back may be the value or the JSON text of the value,
23301
+ * depending on how it was written — a projection writes the
23302
+ * value, a hand edit through a plain text field writes the text.
23303
+ * `flag` is an array that a single-flag edit can leave as a bare
23304
+ * string. Reading these without saying so is how a hook drawn
23305
+ * from `{"users":"department"}` becomes no hook at all.
23306
+ ***************************************************************/
23307
+ function as_json(value) {
23308
+ if (typeof value !== "string") return value;
23309
+ let text = value.trim();
23310
+ if (text.length === 0) return null;
23311
+ try {
23312
+ return JSON.parse(text);
23313
+ } catch (e) {
23314
+ return value;
23315
+ }
23316
+ }
23317
+ function col_flags(record) {
23318
+ let flag = record ? record.flag : null;
23319
+ if (Array.isArray(flag)) return flag.filter((f) => typeof f === "string" && f.length > 0);
23320
+ if (typeof flag === "string" && flag.length > 0) {
23321
+ let parsed = as_json(flag);
23322
+ if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string" && f.length > 0);
23323
+ return [flag];
23324
+ }
23325
+ return [];
23326
+ }
23327
+ function col_hook(record) {
23328
+ let hook = as_json(record ? record.hook : null);
23329
+ if (hook && typeof hook === "object" && !Array.isArray(hook)) return hook;
23330
+ return null;
23331
+ }
23332
+ function col_enum(record) {
23333
+ let e = as_json(record ? record.enum : null);
23334
+ if (Array.isArray(e)) return e;
23335
+ return [];
23336
+ }
23337
+ function topic_pkey2s(record) {
23338
+ let pkey2s = as_json(record ? record.pkey2s : null);
23339
+ if (Array.isArray(pkey2s)) return pkey2s.filter((k) => typeof k === "string" && k.length > 0);
23340
+ if (typeof pkey2s === "string" && pkey2s.length > 0) return [pkey2s];
23341
+ return [];
23342
+ }
23343
+ /***************************************************************
23344
+ * Lookups by id. The editor addresses a position by NAME (the
23345
+ * url carries `<treedb>/<topic>`), so both are offered.
23346
+ ***************************************************************/
23347
+ function find_treedb(model, treedb_id) {
23348
+ if (!model || !Array.isArray(model.treedbs)) return null;
23349
+ for (let treedb of model.treedbs) if (treedb.id === treedb_id) return treedb;
23350
+ return null;
23351
+ }
23352
+ function find_topic(model, treedb_id, topic_name) {
23353
+ let treedb = find_treedb(model, treedb_id);
23354
+ if (!treedb) return null;
23355
+ for (let topic of treedb.topics) if (topic.name === topic_name || topic.id === topic_name) return topic;
23356
+ return null;
23357
+ }
23358
+ function find_col(model, treedb_id, topic_name, col_name) {
23359
+ let topic = find_topic(model, treedb_id, topic_name);
23360
+ if (!topic) return null;
23361
+ for (let col of topic.cols) if (col.name === col_name || col.id === col_name) return col;
23362
+ return null;
23363
+ }
23364
+ /***************************************************************
23365
+ * The fkey a NEW record must carry to belong to `parent_id`.
23366
+ * Written here because the editor is the only writer that has to
23367
+ * compose one, and getting the hook name wrong links the record
23368
+ * to nothing while the write still succeeds.
23369
+ ***************************************************************/
23370
+ function fkey_ref(parent_topic, parent_id_, hook_name) {
23371
+ return `${parent_topic}^${parent_id_}^${hook_name}`;
23372
+ }
23373
+ /***************************************************************
23374
+ * Where a new sibling goes: after the last one. The store's own
23375
+ * default (9999) would put every hand-made column in the same
23376
+ * place and let their names decide the schema's order.
23377
+ ***************************************************************/
23378
+ function next_order(siblings) {
23379
+ let max = 0;
23380
+ if (Array.isArray(siblings)) for (let s of siblings) {
23381
+ let order = s && typeof s.order === "number" ? s.order : DEFAULT_ORDER;
23382
+ if (order < 9999 && order > max) max = order;
23383
+ }
23384
+ return max + 1;
23385
+ }
23386
+ /***************************************************************
23387
+ * Renumber a list AFTER a move, as the writes it takes: only the
23388
+ * records whose order actually changed, so a drag of one row in a
23389
+ * 40-column topic is not 40 writes.
23390
+ *
23391
+ * moved_orders(list) -> [{id, order}, ...]
23392
+ ***************************************************************/
23393
+ function moved_orders(list) {
23394
+ let writes = [];
23395
+ if (!Array.isArray(list)) return writes;
23396
+ for (let i = 0; i < list.length; i++) {
23397
+ let entry = list[i];
23398
+ let order = i + 1;
23399
+ if (!entry || typeof entry.id !== "string") continue;
23400
+ if (entry.order !== order) writes.push({
23401
+ id: entry.id,
23402
+ order
23403
+ });
23404
+ }
23405
+ return writes;
23406
+ }
23407
+ //#endregion
23408
+ //#region src/treedb_topic_keys.js
23409
+ /***********************************************************************
23410
+ * treedb_topic_keys.js
23411
+ *
23412
+ * The KEYS of a treedb topic, read from its desc: the pkey, the
23413
+ * secondary keys (`pkey2s`) and the time key (`tkey`), plus the
23414
+ * names behind its `system_flag` number.
23415
+ *
23416
+ * Every view that describes a topic -- the schema diagram, the
23417
+ * topic-info panel, the topic cards -- must say them, and say them
23418
+ * the same way. A topic with `pkey2s` is not a topic with one more
23419
+ * column: one id holds several INSTANCES, an update has to carry
23420
+ * the pkey2 or it lands on the primary, and `delete-instance`
23421
+ * exists. `tkey` names where the time of every record comes from.
23422
+ * The views said neither, so this is where they ask.
23423
+ *
23424
+ * Pure, so it is tested with no DOM.
23425
+ *
23426
+ * Copyright (c) 2026, ArtGins.
23427
+ * All Rights Reserved.
23428
+ ***********************************************************************/
23429
+ var SF_NAMES = [
23430
+ "sf_string_key",
23431
+ "sf_rowid_key",
23432
+ "sf_int_key",
23433
+ "",
23434
+ "sf_zip_record",
23435
+ "sf_cipher_record",
23436
+ "",
23437
+ "",
23438
+ "sf_t_ms",
23439
+ "sf_tm_ms",
23440
+ "sf_deleted_instance",
23441
+ "sf_immutable_record",
23442
+ "sf_loading_from_disk",
23443
+ "",
23444
+ "",
23445
+ ""
23446
+ ];
23447
+ /************************************************************
23448
+ * The secondary keys of a topic, always a list. The desc
23449
+ * carries a list; a schema literal may declare a bare
23450
+ * string.
23451
+ ************************************************************/
23452
+ function desc_pkey2s(desc) {
23453
+ return topic_pkey2s(desc);
23454
+ }
23455
+ /************************************************************
23456
+ * The time key of a topic, "" when it has none -- and then
23457
+ * the time of a record is the time it was appended.
23458
+ ************************************************************/
23459
+ function desc_tkey(desc) {
23460
+ let tkey = desc ? desc.tkey : "";
23461
+ return typeof tkey === "string" ? tkey : "";
23462
+ }
23463
+ /************************************************************
23464
+ * Which keys of its topic a column IS, in the order the
23465
+ * views print them: "pkey", "pkey2", "tkey". Empty for a
23466
+ * column that is none.
23467
+ ************************************************************/
23468
+ function col_key_roles(desc, col_id) {
23469
+ let roles = [];
23470
+ if (!desc || typeof col_id !== "string" || col_id.length === 0) return roles;
23471
+ if (col_id === (desc.pkey || "id")) roles.push("pkey");
23472
+ if (desc_pkey2s(desc).includes(col_id)) roles.push("pkey2");
23473
+ if (desc_tkey(desc) === col_id) roles.push("tkey");
23474
+ return roles;
23475
+ }
23476
+ /************************************************************
23477
+ * The names of a system_flag. The desc carries the NUMBER
23478
+ * (`1`), a schema literal carries the names
23479
+ * ("sf_string_key|sf_t_ms"); both come back as a list. A
23480
+ * set bit with no name is shown by its value, never
23481
+ * dropped.
23482
+ ************************************************************/
23483
+ function system_flag_names(system_flag) {
23484
+ if (Array.isArray(system_flag)) return system_flag.filter((n) => typeof n === "string" && n.length > 0);
23485
+ if (typeof system_flag === "string") return system_flag.split(/[|,]/).map((n) => n.trim()).filter((n) => n.length > 0);
23486
+ if (typeof system_flag !== "number" || !Number.isInteger(system_flag) || system_flag <= 0) return [];
23487
+ let names = [];
23488
+ for (let bit = 0; bit < SF_NAMES.length; bit++) {
23489
+ let mask = 1 << bit;
23490
+ if (system_flag & mask) names.push(SF_NAMES[bit] || `0x${mask.toString(16).padStart(4, "0")}`);
23491
+ }
23492
+ return names;
23493
+ }
23494
+ //#endregion
23054
23495
  //#region src/c_yui_treedb_topics.js
23055
23496
  /***********************************************************************
23056
23497
  * c_yui_treedb_topics.js
23057
23498
  *
23058
23499
  * Management of TreeDB's topics with Bulma tabs
23059
23500
  *
23060
- * Copyright (c) 2025, ArtGins.
23501
+ * Copyright (c) 2025-2026, ArtGins.
23061
23502
  * All Rights Reserved.
23062
23503
  ***********************************************************************/
23063
23504
  /***************************************************************
@@ -23571,6 +24012,29 @@ function topic_card_meta(desc) {
23571
24012
  { class: "TREEDB_TOPIC_CARD_META" },
23572
24013
  head
23573
24014
  ]);
24015
+ let pkey2s = desc_pkey2s(desc);
24016
+ if (pkey2s.length) {
24017
+ let list = pkey2s.join(", ");
24018
+ meta.push([
24019
+ "div",
24020
+ {
24021
+ class: "TREEDB_TOPIC_CARD_REL TREEDB_TOPIC_CARD_PKEY2S",
24022
+ title: list
24023
+ },
24024
+ [[
24025
+ "span",
24026
+ {
24027
+ class: "TREEDB_TOPIC_CARD_REL_LABEL",
24028
+ i18n: "pkey2s"
24029
+ },
24030
+ "pkey2s"
24031
+ ], [
24032
+ "span",
24033
+ { class: "TREEDB_TOPIC_CARD_REL_LIST" },
24034
+ list
24035
+ ]]
24036
+ ]);
24037
+ }
23574
24038
  let relation = (cls, key, arrow, names) => {
23575
24039
  let list = names.join(", ");
23576
24040
  return [
@@ -23834,33 +24298,26 @@ function show_topic_info(gobj, topic) {
23834
24298
  }
23835
24299
  /************************************************************
23836
24300
  * Build the read-only topic-info panel from a topic desc: the
23837
- * topic name, its pkey, and a table of columns (name / type /
24301
+ * topic name, its KEYS, and a table of columns (name / type /
23838
24302
  * key relationship). Everything is guarded — a malformed desc
23839
24303
  * renders a shorter panel, never throws.
24304
+ *
24305
+ * The keys are what an operator has to know before touching a
24306
+ * record, so they are all said, and the empty ones too: a
24307
+ * topic with `pkey2s` keeps several instances under one id,
24308
+ * and an empty `tkey` means the time of a record is the time
24309
+ * it was appended -- a row missing is not the same as "none".
23840
24310
  ************************************************************/
23841
24311
  function build_topic_info_panel(gobj, $info, topic, desc) {
23842
24312
  while ($info.firstChild) $info.removeChild($info.firstChild);
23843
24313
  let pkey = desc.pkey || "";
23844
- let fmt_flag = (v) => {
23845
- if ((0, _yuneta_gobj_js.is_array)(v)) return v.join(", ");
23846
- if ((0, _yuneta_gobj_js.is_object)(v)) return Object.keys(v).join(", ");
23847
- return v === void 0 || v === null ? "" : String(v);
23848
- };
24314
+ let pkey2s = desc_pkey2s(desc);
24315
+ let tkey = desc_tkey(desc);
23849
24316
  let $meta = [];
23850
- let push_meta = (key, value, highlight) => {
23851
- if (value === void 0 || value === null || value === "") return;
23852
- let $val = highlight ? [
23853
- "span",
23854
- { class: "tag is-info" },
23855
- `${value}`
23856
- ] : [
23857
- "code",
23858
- {},
23859
- `${value}`
23860
- ];
24317
+ let push_meta = (key, $val) => {
23861
24318
  $meta.push([
23862
24319
  "tr",
23863
- {},
24320
+ { class: `TREEDB_TOPIC_INFO_${key.toUpperCase()}` },
23864
24321
  [[
23865
24322
  "th",
23866
24323
  {
@@ -23875,29 +24332,64 @@ function build_topic_info_panel(gobj, $info, topic, desc) {
23875
24332
  ]]
23876
24333
  ]);
23877
24334
  };
23878
- push_meta("version", desc.topic_version, true);
23879
- push_meta("system", fmt_flag(desc.system_flag));
23880
- push_meta("pkey", pkey);
23881
- push_meta("tkey", desc.tkey);
24335
+ if (desc.topic_version !== void 0 && desc.topic_version !== null && desc.topic_version !== "") push_meta("version", [
24336
+ "span",
24337
+ { class: "tag is-info" },
24338
+ `${desc.topic_version}`
24339
+ ]);
24340
+ let sf_names = system_flag_names(desc.system_flag);
24341
+ if (sf_names.length > 0) push_meta("system", [
24342
+ "code",
24343
+ { title: `${desc.system_flag}` },
24344
+ sf_names.join(" | ")
24345
+ ]);
24346
+ if (pkey) push_meta("pkey", [
24347
+ "code",
24348
+ {},
24349
+ pkey
24350
+ ]);
24351
+ if (pkey2s.length > 0) push_meta("pkey2s", [
24352
+ "span",
24353
+ { class: "tags" },
24354
+ pkey2s.map((name) => [
24355
+ "span",
24356
+ { class: "tag is-warning" },
24357
+ name
24358
+ ])
24359
+ ]);
24360
+ push_meta("tkey", tkey ? [
24361
+ "code",
24362
+ {},
24363
+ tkey
24364
+ ] : [
24365
+ "span",
24366
+ {
24367
+ class: "has-text-grey",
24368
+ i18n: "append time"
24369
+ },
24370
+ "append time"
24371
+ ]);
23882
24372
  let $rows = [];
23883
24373
  let cols = (0, _yuneta_gobj_js.is_array)(desc.cols) ? desc.cols : [];
23884
24374
  for (let col of cols) {
23885
24375
  if (!col || !col.id || col.id.charAt(0) === "_") continue;
23886
24376
  let type = col.type || ((0, _yuneta_gobj_js.is_array)(col.flag) ? col.flag.join(", ") : col.flag || "");
23887
- let rel = "";
23888
- if (col.id === pkey) rel = "pkey";
23889
- else if ((0, _yuneta_gobj_js.is_object)(col.fkey)) rel = "→ " + Object.keys(col.fkey).join(", ");
23890
- else if ((0, _yuneta_gobj_js.is_object)(col.hook)) rel = "hook → " + Object.keys(col.hook).join(", ");
24377
+ let roles = col_key_roles(desc, col.id);
24378
+ let parts = roles.slice();
24379
+ if ((0, _yuneta_gobj_js.is_object)(col.fkey)) parts.push("→ " + Object.keys(col.fkey).join(", "));
24380
+ else if ((0, _yuneta_gobj_js.is_object)(col.hook)) parts.push("hook → " + Object.keys(col.hook).join(", "));
24381
+ let rel = parts.join(" · ");
24382
+ let is_key = roles.length > 0;
23891
24383
  $rows.push([
23892
24384
  "tr",
23893
- {},
24385
+ is_key ? { class: "TREEDB_TOPIC_INFO_KEY_COL" } : {},
23894
24386
  [
23895
24387
  [
23896
24388
  "td",
23897
24389
  {},
23898
24390
  [[
23899
24391
  "code",
23900
- {},
24392
+ is_key ? { class: "has-text-weight-bold" } : {},
23901
24393
  `${col.id}`
23902
24394
  ]]
23903
24395
  ],
@@ -23908,7 +24400,7 @@ function build_topic_info_panel(gobj, $info, topic, desc) {
23908
24400
  ],
23909
24401
  [
23910
24402
  "td",
23911
- {},
24403
+ is_key ? { class: "has-text-weight-semibold" } : {},
23912
24404
  `${rel}`
23913
24405
  ]
23914
24406
  ]
@@ -25643,361 +26135,6 @@ function row_matches(row, term) {
25643
26135
  });
25644
26136
  }
25645
26137
  //#endregion
25646
- //#region src/schema_model.js
25647
- /***********************************************************************
25648
- * schema_model.js
25649
- *
25650
- * A SCHEMA out of the three meta-topics that store it.
25651
- *
25652
- * `treedb_system_schema` keeps every schema of a yuno as DATA, in
25653
- * three flat topics — `treedbs`, `topics`, `cols` — linked by
25654
- * fkeys. That is the right STORAGE and the wrong thing to put in
25655
- * front of an operator: adding one column to one topic means
25656
- * finding it in a table holding every column of every topic of
25657
- * every treedb the yuno has.
25658
- *
25659
- * This module turns those three lists back into what they
25660
- * describe: treedb -> topics -> columns, each in its declared
25661
- * `order`. Pure, so the editor's model can be tested with no DOM
25662
- * and no backend — the gclass fetches and draws, this decides
25663
- * what the records MEAN.
25664
- *
25665
- * TWO THINGS IT EXISTS TO STATE ONCE:
25666
- *
25667
- * 1. A record is grouped by its FKEY, not by its id. The id is
25668
- * qualified (`<treedb>.<topic>.<column>`) and reading it back
25669
- * with a split on '.' works right up to the first treedb or
25670
- * topic whose name carries one. The fkey is the link the store
25671
- * itself uses; the id is only the fallback for a record whose
25672
- * fkey never arrived.
25673
- *
25674
- * 2. A fkey reaches this code in FOUR shapes, because `nodes`
25675
- * answers differently depending on the options it was called
25676
- * with: the bare ref string, the expanded object, a list of
25677
- * either, or a dict keyed by the ref. All four are the same
25678
- * fact and parse_fkey_ref() flattens them to it.
25679
- *
25680
- * Copyright (c) 2026, ArtGins.
25681
- * All Rights Reserved.
25682
- ***********************************************************************/
25683
- var DEFAULT_ORDER = 9999;
25684
- /***************************************************************
25685
- * parse_fkey_ref(value) -> [{topic_name, id, hook_name}, ...]
25686
- *
25687
- * Every shape a fkey column can arrive in, flattened to the
25688
- * refs it names. Anything unrecognizable contributes nothing —
25689
- * a schema drawn without one link beats a screen that throws.
25690
- ***************************************************************/
25691
- function parse_fkey_ref(value) {
25692
- let refs = [];
25693
- let push_one = (v) => {
25694
- if (typeof v === "string") {
25695
- let parts = v.split("^");
25696
- if (parts.length !== 3) return;
25697
- refs.push({
25698
- topic_name: parts[0],
25699
- id: parts[1],
25700
- hook_name: parts[2]
25701
- });
25702
- return;
25703
- }
25704
- if (v && typeof v === "object") {
25705
- let id = v.id;
25706
- if (typeof id !== "string" || id.length === 0) return;
25707
- refs.push({
25708
- topic_name: v.topic_name || "",
25709
- id,
25710
- hook_name: v.hook_name || ""
25711
- });
25712
- }
25713
- };
25714
- if (value === null || value === void 0) return refs;
25715
- if (typeof value === "string") {
25716
- push_one(value);
25717
- return refs;
25718
- }
25719
- if (Array.isArray(value)) {
25720
- for (let v of value) push_one(v);
25721
- return refs;
25722
- }
25723
- if (typeof value === "object") for (let [key, v] of Object.entries(value)) if (v && typeof v === "object" && typeof v.id === "string") push_one(v);
25724
- else push_one(key);
25725
- return refs;
25726
- }
25727
- /***************************************************************
25728
- * The id of the FIRST parent a record names, or "" when it
25729
- * names none. A record has one parent here — a column belongs to
25730
- * one topic, a topic to one treedb — and a second ref would be a
25731
- * store this editor cannot draw anyway.
25732
- ***************************************************************/
25733
- function parent_id(record, fkey_col) {
25734
- let refs = parse_fkey_ref(record ? record[fkey_col] : null);
25735
- return refs.length > 0 ? refs[0].id : "";
25736
- }
25737
- /***************************************************************
25738
- * The name a record is KNOWN by, which is not what it is keyed
25739
- * by: `topics` and `cols` key by the qualified id and carry the
25740
- * bare name in `value` (its pkey2). An older store that still
25741
- * keyed by rowid has no `value` worth showing, so the id stands
25742
- * in — the same fallback as node_label().
25743
- ***************************************************************/
25744
- function record_name(record) {
25745
- if (!record) return "";
25746
- if (typeof record.value === "string" && record.value.length > 0) return record.value;
25747
- return typeof record.id === "string" ? record.id : "";
25748
- }
25749
- /***************************************************************
25750
- * The `order` of a record as a NUMBER, whatever the store put
25751
- * there: an integer column read back from JSON may arrive as a
25752
- * string, and comparing "10" with 9 sorts the schema wrong.
25753
- ***************************************************************/
25754
- function record_order(record) {
25755
- let order = record ? record.order : void 0;
25756
- if (typeof order === "number" && isFinite(order)) return order;
25757
- if (typeof order === "string" && order.trim().length > 0) {
25758
- let n = Number(order);
25759
- if (isFinite(n)) return n;
25760
- }
25761
- return DEFAULT_ORDER;
25762
- }
25763
- /***************************************************************
25764
- * Sort in place by `order`, ties broken by name so a schema
25765
- * whose records all default to 9999 still draws the same way
25766
- * twice.
25767
- ***************************************************************/
25768
- function sort_by_order(list) {
25769
- list.sort((a, b) => {
25770
- if (a.order !== b.order) return a.order - b.order;
25771
- return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
25772
- });
25773
- return list;
25774
- }
25775
- /***************************************************************
25776
- * build_schema_model({treedbs, topics, cols}) -> model
25777
- *
25778
- * model = {
25779
- * treedbs: [{
25780
- * id, name, record,
25781
- * schema_version, c_schema_version, system_schema_version,
25782
- * topics: [{
25783
- * id, name, order, record,
25784
- * pkey, topic_version, system_topic, main_topic,
25785
- * cols: [{id, name, order, record}]
25786
- * }]
25787
- * }],
25788
- * orphan_topics: [...], topics whose treedb is not in the store
25789
- * orphan_cols: [...] columns whose topic is not in the store
25790
- * }
25791
- *
25792
- * An orphan is NOT dropped. A column whose topic was deleted is
25793
- * exactly the kind of leftover an operator opens this editor to
25794
- * find, and a model that hides it makes the editor lie.
25795
- ***************************************************************/
25796
- function build_schema_model(records) {
25797
- let r = records || {};
25798
- let treedb_rows = Array.isArray(r.treedbs) ? r.treedbs : [];
25799
- let topic_rows = Array.isArray(r.topics) ? r.topics : [];
25800
- let col_rows = Array.isArray(r.cols) ? r.cols : [];
25801
- let treedbs = [];
25802
- let by_treedb_id = {};
25803
- for (let record of treedb_rows) {
25804
- if (!record || typeof record.id !== "string") continue;
25805
- let entry = {
25806
- id: record.id,
25807
- name: record.id,
25808
- record,
25809
- schema_version: record.schema_version,
25810
- c_schema_version: record.c_schema_version,
25811
- system_schema_version: record.system_schema_version,
25812
- topics: []
25813
- };
25814
- treedbs.push(entry);
25815
- by_treedb_id[entry.id] = entry;
25816
- }
25817
- let orphan_topics = [];
25818
- let by_topic_id = {};
25819
- for (let record of topic_rows) {
25820
- if (!record || typeof record.id !== "string") continue;
25821
- let entry = {
25822
- id: record.id,
25823
- name: record_name(record),
25824
- order: record_order(record),
25825
- record,
25826
- treedb_id: parent_id(record, "treedbs"),
25827
- pkey: record.pkey || "",
25828
- pkey2s: record.pkey2s,
25829
- system_flag: record.system_flag || "",
25830
- tkey: record.tkey || "",
25831
- topic_version: record.topic_version,
25832
- system_topic: !!record.system_topic,
25833
- main_topic: !!record.main_topic,
25834
- cols: []
25835
- };
25836
- by_topic_id[entry.id] = entry;
25837
- let parent = by_treedb_id[entry.treedb_id];
25838
- if (parent) parent.topics.push(entry);
25839
- else orphan_topics.push(entry);
25840
- }
25841
- let orphan_cols = [];
25842
- for (let record of col_rows) {
25843
- if (!record || typeof record.id !== "string") continue;
25844
- let entry = {
25845
- id: record.id,
25846
- name: record_name(record),
25847
- order: record_order(record),
25848
- record,
25849
- topic_id: parent_id(record, "topics")
25850
- };
25851
- let parent = by_topic_id[entry.topic_id];
25852
- if (parent) parent.cols.push(entry);
25853
- else orphan_cols.push(entry);
25854
- }
25855
- for (let treedb of treedbs) {
25856
- sort_by_order(treedb.topics);
25857
- for (let topic of treedb.topics) sort_by_order(topic.cols);
25858
- }
25859
- for (let topic of orphan_topics) sort_by_order(topic.cols);
25860
- treedbs.sort((a, b) => {
25861
- return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
25862
- });
25863
- sort_by_order(orphan_topics);
25864
- sort_by_order(orphan_cols);
25865
- return {
25866
- treedbs,
25867
- orphan_topics,
25868
- orphan_cols
25869
- };
25870
- }
25871
- /***************************************************************
25872
- * Is there a value here at all?
25873
- *
25874
- * The store answers a `blob` column that was never set with an
25875
- * EMPTY COLLECTION, not with nothing: `enum: {}`, `hook: {}`,
25876
- * `default: {}`. Read as "a value", those turn every export into
25877
- * a schema full of empty objects — and `'hook': {}` in a literal
25878
- * is a hook with no mapping, which is a link the treedb builds
25879
- * and nothing writes.
25880
- ***************************************************************/
25881
- function is_empty_value(value) {
25882
- if (value === null || value === void 0 || value === "") return true;
25883
- if (Array.isArray(value)) return value.length === 0;
25884
- if (typeof value === "object") return Object.keys(value).length === 0;
25885
- return false;
25886
- }
25887
- /***************************************************************
25888
- * The fields of a column that are DECLARED as one thing and
25889
- * STORED as another.
25890
- *
25891
- * `hook`, `enum`, `default`, `pkey2s` and `properties` are `blob`
25892
- * columns of the `cols` topic (treedb_system_schema.c), so what
25893
- * comes back may be the value or the JSON text of the value,
25894
- * depending on how it was written — a projection writes the
25895
- * value, a hand edit through a plain text field writes the text.
25896
- * `flag` is an array that a single-flag edit can leave as a bare
25897
- * string. Reading these without saying so is how a hook drawn
25898
- * from `{"users":"department"}` becomes no hook at all.
25899
- ***************************************************************/
25900
- function as_json(value) {
25901
- if (typeof value !== "string") return value;
25902
- let text = value.trim();
25903
- if (text.length === 0) return null;
25904
- try {
25905
- return JSON.parse(text);
25906
- } catch (e) {
25907
- return value;
25908
- }
25909
- }
25910
- function col_flags(record) {
25911
- let flag = record ? record.flag : null;
25912
- if (Array.isArray(flag)) return flag.filter((f) => typeof f === "string" && f.length > 0);
25913
- if (typeof flag === "string" && flag.length > 0) {
25914
- let parsed = as_json(flag);
25915
- if (Array.isArray(parsed)) return parsed.filter((f) => typeof f === "string" && f.length > 0);
25916
- return [flag];
25917
- }
25918
- return [];
25919
- }
25920
- function col_hook(record) {
25921
- let hook = as_json(record ? record.hook : null);
25922
- if (hook && typeof hook === "object" && !Array.isArray(hook)) return hook;
25923
- return null;
25924
- }
25925
- function col_enum(record) {
25926
- let e = as_json(record ? record.enum : null);
25927
- if (Array.isArray(e)) return e;
25928
- return [];
25929
- }
25930
- function topic_pkey2s(record) {
25931
- let pkey2s = as_json(record ? record.pkey2s : null);
25932
- if (Array.isArray(pkey2s)) return pkey2s.filter((k) => typeof k === "string" && k.length > 0);
25933
- if (typeof pkey2s === "string" && pkey2s.length > 0) return [pkey2s];
25934
- return [];
25935
- }
25936
- /***************************************************************
25937
- * Lookups by id. The editor addresses a position by NAME (the
25938
- * url carries `<treedb>/<topic>`), so both are offered.
25939
- ***************************************************************/
25940
- function find_treedb(model, treedb_id) {
25941
- if (!model || !Array.isArray(model.treedbs)) return null;
25942
- for (let treedb of model.treedbs) if (treedb.id === treedb_id) return treedb;
25943
- return null;
25944
- }
25945
- function find_topic(model, treedb_id, topic_name) {
25946
- let treedb = find_treedb(model, treedb_id);
25947
- if (!treedb) return null;
25948
- for (let topic of treedb.topics) if (topic.name === topic_name || topic.id === topic_name) return topic;
25949
- return null;
25950
- }
25951
- function find_col(model, treedb_id, topic_name, col_name) {
25952
- let topic = find_topic(model, treedb_id, topic_name);
25953
- if (!topic) return null;
25954
- for (let col of topic.cols) if (col.name === col_name || col.id === col_name) return col;
25955
- return null;
25956
- }
25957
- /***************************************************************
25958
- * The fkey a NEW record must carry to belong to `parent_id`.
25959
- * Written here because the editor is the only writer that has to
25960
- * compose one, and getting the hook name wrong links the record
25961
- * to nothing while the write still succeeds.
25962
- ***************************************************************/
25963
- function fkey_ref(parent_topic, parent_id_, hook_name) {
25964
- return `${parent_topic}^${parent_id_}^${hook_name}`;
25965
- }
25966
- /***************************************************************
25967
- * Where a new sibling goes: after the last one. The store's own
25968
- * default (9999) would put every hand-made column in the same
25969
- * place and let their names decide the schema's order.
25970
- ***************************************************************/
25971
- function next_order(siblings) {
25972
- let max = 0;
25973
- if (Array.isArray(siblings)) for (let s of siblings) {
25974
- let order = s && typeof s.order === "number" ? s.order : DEFAULT_ORDER;
25975
- if (order < 9999 && order > max) max = order;
25976
- }
25977
- return max + 1;
25978
- }
25979
- /***************************************************************
25980
- * Renumber a list AFTER a move, as the writes it takes: only the
25981
- * records whose order actually changed, so a drag of one row in a
25982
- * 40-column topic is not 40 writes.
25983
- *
25984
- * moved_orders(list) -> [{id, order}, ...]
25985
- ***************************************************************/
25986
- function moved_orders(list) {
25987
- let writes = [];
25988
- if (!Array.isArray(list)) return writes;
25989
- for (let i = 0; i < list.length; i++) {
25990
- let entry = list[i];
25991
- let order = i + 1;
25992
- if (!entry || typeof entry.id !== "string") continue;
25993
- if (entry.order !== order) writes.push({
25994
- id: entry.id,
25995
- order
25996
- });
25997
- }
25998
- return writes;
25999
- }
26000
- //#endregion
26001
26138
  //#region src/treedb_write_plan.js
26002
26139
  /***********************************************************************
26003
26140
  * treedb_write_plan.js
@@ -68843,12 +68980,20 @@ function hook_mark(type) {
68843
68980
  * (↖) 1 fkey (1 parent)
68844
68981
  * [↖] n fkeys (n parents)
68845
68982
  * {↖} N fkeys (N parents)
68983
+ * (2) a secondary key (pkey2)
68984
+ * (t) the time key (tkey)
68846
68985
  * * required
68847
68986
  * # the primary key
68848
68987
  *
68849
- * A column can be BOTH hook and fkey, so the flags are read
68850
- * here rather than through treedb_get_field_desc's single
68851
- * `type`, which keeps only the last flag it saw.
68988
+ * The key marks lead the mark column, and a pkey2 is drawn in
68989
+ * bold like the pkey: a topic with secondary keys keeps several
68990
+ * instances under one id, which is the first thing to know
68991
+ * about it. `(t)` is new to the notation: the literals' legend
68992
+ * carries it too.
68993
+ *
68994
+ * The flags are read here rather than through
68995
+ * treedb_get_field_desc's single `type`, which keeps only the
68996
+ * last flag it saw.
68852
68997
  ************************************************************/
68853
68998
  function schema_rows(desc) {
68854
68999
  let rows = [];
@@ -68858,19 +69003,25 @@ function schema_rows(desc) {
68858
69003
  let flags = Array.isArray(col.flag) ? col.flag : [];
68859
69004
  let is_hook = flags.indexOf("hook") >= 0;
68860
69005
  let is_fkey = flags.indexOf("fkey") >= 0;
69006
+ let roles = col_key_roles(desc, col.id);
69007
+ let key_marks = [];
69008
+ if (roles.includes("pkey2")) key_marks.push("(2)");
69009
+ if (roles.includes("tkey")) key_marks.push("(t)");
68861
69010
  let mark = "";
68862
69011
  if (is_hook) mark = hook_mark(col.type);
68863
69012
  if (is_fkey) {
68864
69013
  let fmark = hook_mark(col.type).replace("}", "↖}").replace("]", "↖]").replace(")", "↖)");
68865
69014
  mark = mark ? mark + " " + fmark : fmark;
68866
69015
  }
69016
+ if (key_marks.length) mark = mark ? key_marks.join(" ") + " " + mark : key_marks.join(" ");
68867
69017
  rows.push({
68868
69018
  name: col.id,
68869
69019
  type: col.type || "",
68870
69020
  mark,
68871
69021
  is_hook,
68872
69022
  is_fkey,
68873
- is_pkey: col.id === desc.pkey,
69023
+ is_pkey: roles.includes("pkey"),
69024
+ is_pkey2: roles.includes("pkey2"),
68874
69025
  required: flags.indexOf("required") >= 0 || flags.indexOf("notnull") >= 0
68875
69026
  });
68876
69027
  }
@@ -68917,7 +69068,7 @@ function build_card_innerHTML(topic, rows, color, dark) {
68917
69068
  let rows_html = "";
68918
69069
  for (let row of rows) {
68919
69070
  let lead = row.is_pkey ? "#" : row.required ? "*" : "";
68920
- let name_weight = row.is_pkey || row.required ? "600" : "400";
69071
+ let name_weight = row.is_pkey || row.is_pkey2 ? "700" : row.required ? "600" : "400";
68921
69072
  let name_color = row.is_hook || row.is_fkey ? title_color : text_color;
68922
69073
  rows_html += ` <div class="TREEDB_SCHEMA_FIELD" style="
68923
69074
  display: flex; align-items: center; gap: 6px;
@@ -76005,86 +76156,6 @@ function register_c_yui_period() {
76005
76156
  return create_gclass(GCLASS_NAME);
76006
76157
  }
76007
76158
  //#endregion
76008
- //#region src/dev_machine_trace.js
76009
- /***********************************************************************
76010
- * dev_machine_trace.js
76011
- *
76012
- * Reading a MACHINE trace line back.
76013
- *
76014
- * The developer window mirrors the runtime's log, and the
76015
- * `machine` trace is most of what lands there once it is on.
76016
- * Those lines arrive as text, so to the window they were just
76017
- * "a debug log" -- which is why its `Periodic` filter could
76018
- * not touch them: the filter works on a signature, and every
76019
- * mirrored log carried the same one (`log:debug`).
76020
- *
76021
- * This gives a machine line back its EVENT, which is the only
76022
- * thing that says whether it is noise. Both formats gobj-js
76023
- * emits (`gobj_set_trace_machine_format`):
76024
- *
76025
- * 0 verbose "🔄 mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ac: …"
76026
- * "<- mach(C_TIMER^t), st: ST_IDLE, ev: EV_TIMEOUT, ret: 0"
76027
- * 1 compact "🔄 EV_TIMEOUT C_TIMER^t ST_IDLE from C_TIMER^t"
76028
- *
76029
- * Copyright (c) 2026, ArtGins.
76030
- * All Rights Reserved.
76031
- ***********************************************************************/
76032
- var MACHINE_MARK_RE = /\bmach\(/;
76033
- var EVENT_FIELD_RE = /\bev:\s*([A-Za-z0-9_]+)/;
76034
- var EVENT_HEAD_RE = /^\s*\S{0,4}\s*(EV_[A-Za-z0-9_]+)(?=\s|$)/;
76035
- var PERIODIC_EVENT_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING|POLL/i;
76036
- /************************************************************
76037
- * The event of a machine trace line, or "" when the line is
76038
- * not one.
76039
- *
76040
- * "" is also the answer for a machine line that carries no
76041
- * event -- a state change ("new st(…), prev st(…)") -- and
76042
- * that is right: what it reports is not repetitive.
76043
- ************************************************************/
76044
- function machine_event_of(text) {
76045
- if (typeof text !== "string" || text.length === 0) return "";
76046
- if (MACHINE_MARK_RE.test(text)) {
76047
- let m = EVENT_FIELD_RE.exec(text);
76048
- return m ? m[1] : "";
76049
- }
76050
- let m = EVENT_HEAD_RE.exec(text);
76051
- return m ? m[1] : "";
76052
- }
76053
- /************************************************************
76054
- * Is this event one of the ones that arrive by themselves,
76055
- * for ever, and say nothing about what the app is doing?
76056
- ************************************************************/
76057
- function is_periodic_event(event_name) {
76058
- if (!event_name) return false;
76059
- return PERIODIC_EVENT_RE.test(event_name);
76060
- }
76061
- /************************************************************
76062
- * The signature of a mirrored log line: what the window
76063
- * counts it under and what its filters match on.
76064
- *
76065
- * A machine line is signed by its EVENT, so a hundred
76066
- * EV_TIMEOUTs are one recurring thing rather than a hundred
76067
- * anonymous debug lines. Everything else is signed by its
76068
- * level, as before.
76069
- ************************************************************/
76070
- function log_signature(level, text) {
76071
- let event = machine_event_of(text);
76072
- if (event) return "mach:" + event;
76073
- return "log:" + (level || "debug");
76074
- }
76075
- /************************************************************
76076
- * May the periodic filter hide this log line?
76077
- *
76078
- * Only a machine transition, and only below warning level.
76079
- * An ERROR that names EV_TIMEOUT -- "Event NOT DEFINED in
76080
- * state", which is exactly the loud one the framework wants
76081
- * seen -- must never disappear because of the word in it.
76082
- ************************************************************/
76083
- function log_is_periodic(level, text) {
76084
- if (level === "error" || level === "warning") return false;
76085
- return is_periodic_event(machine_event_of(text));
76086
- }
76087
- //#endregion
76088
76159
  //#region src/yui_dev.js
76089
76160
  /***********************************************************************
76090
76161
  * ui_dev.js
@@ -76102,10 +76173,7 @@ function log_is_periodic(level, text) {
76102
76173
  * losing history. Reopening the window repaints the buffer.
76103
76174
  ***********************************************************************/
76104
76175
  var TRAFFIC_MAX = 600;
76105
- var PERIODIC_THRESHOLD = 5;
76106
- var PERIODIC_RE = /PERIODIC|TIMEOUT|HEARTBEAT|PING/i;
76107
76176
  var TRAFFIC_LOG = [];
76108
- var TRAFFIC_COUNTS = /* @__PURE__ */ new Map();
76109
76177
  var SEARCH_TEXT = "";
76110
76178
  var LOG_ERR_COUNT = 0;
76111
76179
  var LOG_WARN_COUNT = 0;
@@ -76122,39 +76190,53 @@ var TRAFFIC_TS_FIELDS = {
76122
76190
  };
76123
76191
  var TRACE_DEFS = [
76124
76192
  [
76125
- "trace_automata",
76126
76193
  "automata",
76127
- trace_automata
76194
+ "automata",
76195
+ "trace every event of every automaton",
76196
+ automata_state,
76197
+ toggle_automata
76128
76198
  ],
76129
76199
  [
76130
- "trace_creation",
76131
76200
  "creation",
76132
- trace_creation
76201
+ "creation",
76202
+ "trace the creation and destruction of gobjs",
76203
+ () => global_bit(_yuneta_gobj_js.trace_level_t.TRACE_CREATE_DELETE),
76204
+ () => toggle_global("create_delete")
76133
76205
  ],
76134
76206
  [
76135
- "trace_start_stop",
76207
+ "start_stop",
76136
76208
  "start / stop",
76137
- trace_start_stop
76209
+ "trace the start and stop of gobjs",
76210
+ () => global_bit(_yuneta_gobj_js.trace_level_t.TRACE_START_STOP),
76211
+ () => toggle_global("start_stop")
76138
76212
  ],
76139
76213
  [
76140
- "trace_subscriptions",
76141
76214
  "subscriptions",
76142
- trace_subscriptions
76215
+ "subscriptions",
76216
+ "trace subscriptions and publications",
76217
+ () => global_bit(_yuneta_gobj_js.trace_level_t.TRACE_SUBSCRIPTIONS),
76218
+ () => toggle_global("subscriptions")
76143
76219
  ],
76144
76220
  [
76145
- "trace_i18n",
76146
76221
  "i18n",
76147
- trace_i18n
76222
+ "i18n",
76223
+ "i18next debug output",
76224
+ i18n_state,
76225
+ toggle_i18n
76148
76226
  ],
76149
76227
  [
76150
- "trace_traffic",
76151
76228
  "traffic",
76152
- trace_traffic
76229
+ "traffic",
76230
+ "trace the messages to and from the backend",
76231
+ traffic_state,
76232
+ toggle_traffic
76153
76233
  ],
76154
76234
  [
76155
- "no_poll",
76156
- "no poll",
76157
- set_no_poll
76235
+ "periodic",
76236
+ "periodic",
76237
+ "trace the periodic timer event",
76238
+ periodic_state,
76239
+ toggle_periodic
76158
76240
  ]
76159
76241
  ];
76160
76242
  /******************************
@@ -76221,45 +76303,24 @@ function dev_view() {
76221
76303
  let v = (0, _yuneta_gobj_js.kw_get_local_storage_value)("dev_view_mode", "detailed", false);
76222
76304
  return v === "compact" || v === "name" || v === "full" ? v : "detailed";
76223
76305
  }
76224
- var HIDE_PERIODIC_DEFAULT = 1;
76225
76306
  var SIMPLE_MACH_DEFAULT = 1;
76226
76307
  var SIMPLE_MACH_KEY = "dev_mach_simple";
76227
76308
  function dev_simple_mach() {
76228
76309
  return dev_num(SIMPLE_MACH_KEY, SIMPLE_MACH_DEFAULT) ? 1 : 0;
76229
76310
  }
76230
- function dev_hide_periodic() {
76231
- return dev_num("dev_hide_periodic", HIDE_PERIODIC_DEFAULT) ? true : false;
76232
- }
76233
76311
  function dev_route() {
76234
76312
  let v = (0, _yuneta_gobj_js.kw_get_local_storage_value)("dev_output_route", "both", false);
76235
76313
  return v === "window" || v === "console" ? v : "both";
76236
76314
  }
76237
76315
  function apply_console_route() {
76238
76316
  (0, _yuneta_gobj_js.set_console_log_enabled)(dev_route() !== "window");
76239
- if (typeof _yuneta_gobj_js.set_console_log_filter === "function") _yuneta_gobj_js.set_console_log_filter((level, msg) => {
76240
- if (!dev_hide_periodic()) return true;
76241
- return !log_is_periodic(level, (0, _yuneta_gobj_js.is_string)(msg) ? msg : String(msg));
76242
- });
76243
- else if (!__console_filter_warned__) {
76244
- __console_filter_warned__ = true;
76245
- console.warn("yui_dev: gobj-js has no set_console_log_filter (needs >= 7.13.6): the Periodic filter applies to this window only, not to the console");
76246
- }
76247
76317
  }
76248
- var __console_filter_warned__ = false;
76249
76318
  function console_traffic(title, jn, direction, size) {
76250
76319
  let event = jn && jn.event ? String(jn.event) : "(no event)";
76251
76320
  let kw = jn && jn.kw && typeof jn.kw === "object" ? jn.kw : jn;
76252
76321
  let color = direction === 2 ? "#16a34a" : direction === 3 ? "#dc2626" : "#2563eb";
76253
76322
  window.console.log("%c" + dir_arrow(direction) + " " + (title ? "[" + title + "] " : "") + event + (size ? " (" + size + "b)" : ""), "color:" + color, kw);
76254
76323
  }
76255
- function dev_muted() {
76256
- let a = (0, _yuneta_gobj_js.kw_get_local_storage_value)("dev_muted_events", [], false);
76257
- if (!Array.isArray(a)) a = [];
76258
- return new Set(a);
76259
- }
76260
- function dev_set_muted(set) {
76261
- (0, _yuneta_gobj_js.kw_set_local_storage_value)("dev_muted_events", Array.from(set));
76262
- }
76263
76324
  function set_view(v) {
76264
76325
  (0, _yuneta_gobj_js.kw_set_local_storage_value)("dev_view_mode", v);
76265
76326
  rerender_all();
@@ -76275,54 +76336,25 @@ function set_output_route(v) {
76275
76336
  function toggle_pref(key, def) {
76276
76337
  let v = dev_num(key, def) ? 0 : 1;
76277
76338
  (0, _yuneta_gobj_js.kw_set_local_storage_value)(key, v);
76278
- apply_console_route();
76279
- rerender_all();
76280
- refresh_dev_chrome();
76281
- }
76282
- function mute_signature(sig) {
76283
- let set = dev_muted();
76284
- set.add(sig);
76285
- dev_set_muted(set);
76286
- rerender_all();
76287
- refresh_dev_chrome();
76288
- }
76289
- function unmute_signature(sig) {
76290
- let set = dev_muted();
76291
- set.delete(sig);
76292
- dev_set_muted(set);
76293
76339
  rerender_all();
76294
76340
  refresh_dev_chrome();
76295
76341
  }
76296
76342
  /******************************
76297
76343
  * Filtering
76298
76344
  ******************************/
76299
- function traffic_signature(event, command) {
76300
- return command ? event + " · " + command : event;
76301
- }
76302
- function traffic_is_periodic(sig) {
76303
- if (PERIODIC_RE.test(sig)) return true;
76304
- return (TRAFFIC_COUNTS.get(sig) || 0) >= PERIODIC_THRESHOLD;
76305
- }
76306
76345
  function build_filter_ctx() {
76307
76346
  return {
76308
76347
  out: dev_num("dev_filter_out", 1),
76309
76348
  inc: dev_num("dev_filter_in", 1),
76310
76349
  err: dev_num("dev_filter_err", 1),
76311
- muted: dev_muted(),
76312
- hide_periodic: dev_hide_periodic(),
76313
76350
  search: SEARCH_TEXT
76314
76351
  };
76315
76352
  }
76316
76353
  function entry_hidden(e, ctx) {
76317
- if (e.kind === "log") {
76318
- if (ctx.hide_periodic && e.periodic) return true;
76319
- return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
76320
- }
76354
+ if (e.kind === "log") return !!(ctx.search && e.hay.indexOf(ctx.search) < 0);
76321
76355
  if (e.dir === 1 && !ctx.out) return true;
76322
76356
  if (e.dir === 2 && !ctx.inc) return true;
76323
76357
  if (e.dir === 3 && !ctx.err) return true;
76324
- if (ctx.muted.has(e.sig)) return true;
76325
- if (ctx.hide_periodic && traffic_is_periodic(e.sig)) return true;
76326
76358
  if (ctx.search && e.hay.indexOf(ctx.search) < 0) return true;
76327
76359
  return false;
76328
76360
  }
@@ -76346,11 +76378,6 @@ function ensure_dev_style() {
76346
76378
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
76347
76379
  }
76348
76380
  .YDEV_LOG { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 6px 10px; }
76349
- .YDEV_MUTED {
76350
- display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
76351
- padding: 4px 10px; border-bottom: 1px solid rgba(0,0,0,0.08); font-size: 12px;
76352
- }
76353
- .YDEV_MUTED:empty { display: none; }
76354
76381
  .YDEV_STATS {
76355
76382
  flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 14px;
76356
76383
  padding: 6px 10px; border-top: 1px solid rgba(0,0,0,0.1);
@@ -76382,7 +76409,6 @@ function ensure_dev_style() {
76382
76409
  .YDEV_CHIP.s-in.is-active { background: rgba(5,150,105,0.16); border-color: #059669; color: #059669; }
76383
76410
  .YDEV_CHIP.s-err.is-active { background: rgba(220,38,38,0.16); border-color: #dc2626; color: #dc2626; }
76384
76411
  .YDEV_CHIP[data-dir]:not(.is-active) { opacity: 0.4; text-decoration: line-through; }
76385
- .YDEV_CHIP[data-toggle="periodic"].is-active { background: rgba(217,119,6,0.16); border-color: #d97706; color: #b45309; font-weight: 600; }
76386
76412
  .YDEV_SEG { display: inline-flex; border: 1px solid rgba(0,0,0,0.18); border-radius: 7px; overflow: hidden; }
76387
76413
  .YDEV_SEG_BTN {
76388
76414
  font: inherit; font-size: 12px; padding: 4px 10px; border: 0;
@@ -76396,12 +76422,6 @@ function ensure_dev_style() {
76396
76422
  border: 1px solid rgba(0,0,0,0.18); border-radius: 7px;
76397
76423
  background: transparent; color: inherit;
76398
76424
  }
76399
- .YDEV_MUTED_CHIP {
76400
- font: inherit; font-family: "DejaVu Sans Mono", monospace; font-size: 12px;
76401
- display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px;
76402
- border: 1px solid rgba(217,119,6,0.5); border-radius: 999px;
76403
- background: rgba(217,119,6,0.12); color: #b45309; cursor: pointer;
76404
- }
76405
76425
  .YDEV_STAT.s-out { color: #2563eb; } .YDEV_STAT.s-in { color: #059669; } .YDEV_STAT.s-err { color: #dc2626; }
76406
76426
  .YDEV_STAT.s-warn { color: #d97706; }
76407
76427
  .YDEV_STAT.is-strong { font-weight: 700; }
@@ -76429,10 +76449,7 @@ function ensure_dev_style() {
76429
76449
  .dir-err .TRAFFIC_ARROW, .dir-err .TRAFFIC_EVENT { color: #dc2626; }
76430
76450
  .TRAFFIC_SUMMARY { opacity: 0.6; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
76431
76451
  .TRAFFIC_META { margin-left: auto; opacity: 0.6; font-size: 11px; white-space: nowrap; }
76432
- .TRAFFIC_MUTE { border: 0; background: transparent; color: inherit; cursor: pointer; opacity: 0; font-size: 12px; padding: 0 2px; flex: 0 0 auto; }
76433
- .TRAFFIC_ENTRY:hover .TRAFFIC_MUTE, .TRAFFIC_LINE:hover .TRAFFIC_MUTE, .TRAFFIC_NAME:hover .TRAFFIC_MUTE { opacity: 0.5; }
76434
- .TRAFFIC_MUTE:hover { opacity: 1 !important; color: #d97706; }
76435
- .TRAFFIC_KW { margin: 2px 0 0 16px; }
76452
+ .TRAFFIC_ENTRY:hover .TRAFFIC_KW { margin: 2px 0 0 16px; }
76436
76453
  .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; }
76437
76454
  .TRAFFIC_ROW { display: flex; gap: 6px; align-items: baseline; }
76438
76455
  .TRAFFIC_BULLET { opacity: 0.45; flex: 0 0 auto; }
@@ -76476,9 +76493,7 @@ details.TRAFFIC_NEST > summary::-webkit-details-marker { display: none; }
76476
76493
  :root[data-theme="dark"] .TRAFFIC_VAL.t-bool, :root[data-theme="dark"] .TRAFFIC_VAL.t-null { color: #c084fc; }
76477
76494
  :root[data-theme="dark"] .YDEV_CHIP.is-active { background: rgba(96,165,250,0.2); border-color: #60a5fa; color: #93c5fd; }
76478
76495
  :root[data-theme="dark"] .YDEV_SEG_BTN.is-active { background: #2563eb; color: #fff; }
76479
- :root[data-theme="dark"] .YDEV_CHIP[data-toggle="periodic"].is-active { background: rgba(217,119,6,0.24); border-color: #f59e0b; color: #fbbf24; }
76480
- :root[data-theme="dark"] .YDEV_MUTED_CHIP { border-color: rgba(245,158,11,0.5); background: rgba(245,158,11,0.16); color: #fbbf24; }
76481
- `;
76496
+ :root[data-theme="dark"] `;
76482
76497
  let $style = document.createElement("style");
76483
76498
  $style.id = "yui-dev-style";
76484
76499
  $style.textContent = css;
@@ -76621,23 +76636,6 @@ function traffic_bullets(obj) {
76621
76636
  /******************************
76622
76637
  * Entry rendering (per view)
76623
76638
  ******************************/
76624
- function mute_button(sig) {
76625
- return [
76626
- "button",
76627
- {
76628
- class: "TRAFFIC_MUTE",
76629
- type: "button",
76630
- title: (0, i18next.t)("mute this message"),
76631
- "data-i18n-title": "mute this message"
76632
- },
76633
- "⊘",
76634
- { click: (ev) => {
76635
- ev.stopPropagation();
76636
- ev.preventDefault();
76637
- mute_signature(sig);
76638
- } }
76639
- ];
76640
- }
76641
76639
  function event_spans(e) {
76642
76640
  let spans = [[
76643
76641
  "span",
@@ -76675,7 +76673,6 @@ function compact_summary(kw) {
76675
76673
  }
76676
76674
  function render_detailed(e) {
76677
76675
  let head = event_spans(e);
76678
- head.push(mute_button(e.sig));
76679
76676
  head.push([
76680
76677
  "span",
76681
76678
  { class: "TRAFFIC_META" },
@@ -76734,7 +76731,6 @@ function full_sections(payload) {
76734
76731
  }
76735
76732
  function render_full(e) {
76736
76733
  let head = event_spans(e);
76737
- head.push(mute_button(e.sig));
76738
76734
  head.push([
76739
76735
  "span",
76740
76736
  { class: "TRAFFIC_META" },
@@ -76772,7 +76768,6 @@ function render_compact(e) {
76772
76768
  { class: "TRAFFIC_SUMMARY" },
76773
76769
  compact_summary(e.kw)
76774
76770
  ]);
76775
- kids.push(mute_button(e.sig));
76776
76771
  kids.push([
76777
76772
  "span",
76778
76773
  { class: "TRAFFIC_META" },
@@ -76789,7 +76784,6 @@ function render_compact(e) {
76789
76784
  }
76790
76785
  function render_name(e) {
76791
76786
  let kids = event_spans(e);
76792
- kids.push(mute_button(e.sig));
76793
76787
  kids.push([
76794
76788
  "span",
76795
76789
  { class: "TRAFFIC_META" },
@@ -76846,7 +76840,6 @@ function render_entry(e) {
76846
76840
  ******************************/
76847
76841
  function clear_traffic() {
76848
76842
  TRAFFIC_LOG.length = 0;
76849
- TRAFFIC_COUNTS.clear();
76850
76843
  LOG_ERR_COUNT = 0;
76851
76844
  LOG_WARN_COUNT = 0;
76852
76845
  let logger = document.getElementById("developer-traffic-logger");
@@ -76958,7 +76951,6 @@ function info_traffic(title, msg, direction, size) {
76958
76951
  let event = jn && jn.event ? String(jn.event) : "(no event)";
76959
76952
  let kw = jn && jn.kw && typeof jn.kw === "object" ? jn.kw : null;
76960
76953
  let command = kw && typeof kw.command === "string" ? kw.command : "";
76961
- let sig = traffic_signature(event, command);
76962
76954
  let hay = "";
76963
76955
  try {
76964
76956
  hay = (event + " " + command + " " + (kw ? JSON.stringify(kw) : "")).toLowerCase();
@@ -76969,7 +76961,6 @@ function info_traffic(title, msg, direction, size) {
76969
76961
  title: title || "",
76970
76962
  event,
76971
76963
  command,
76972
- sig,
76973
76964
  dir: direction,
76974
76965
  size,
76975
76966
  ts: traffic_now(),
@@ -76979,17 +76970,11 @@ function info_traffic(title, msg, direction, size) {
76979
76970
  $node: null
76980
76971
  };
76981
76972
  TRAFFIC_LOG.push(entry);
76982
- TRAFFIC_COUNTS.set(sig, (TRAFFIC_COUNTS.get(sig) || 0) + 1);
76983
- let crossed = dev_hide_periodic() && TRAFFIC_COUNTS.get(sig) === PERIODIC_THRESHOLD;
76984
76973
  if (TRAFFIC_LOG.length > TRAFFIC_MAX) {
76985
76974
  let old = TRAFFIC_LOG.shift();
76986
- let c = (TRAFFIC_COUNTS.get(old.sig) || 0) - 1;
76987
- if (c <= 0) TRAFFIC_COUNTS.delete(old.sig);
76988
- else TRAFFIC_COUNTS.set(old.sig, c);
76989
76975
  if (old.$node && old.$node.parentNode) old.$node.parentNode.removeChild(old.$node);
76990
76976
  }
76991
- if (crossed) rerender_all();
76992
- else if (!entry_hidden(entry, build_filter_ctx())) {
76977
+ if (!entry_hidden(entry, build_filter_ctx())) {
76993
76978
  let node = render_entry(entry);
76994
76979
  entry.$node = node;
76995
76980
  let ph = logger.querySelector(".YDEV_EMPTY");
@@ -77035,19 +77020,12 @@ function info_log(level, msg, hora) {
77035
77020
  dir: 0,
77036
77021
  size: 0,
77037
77022
  ts: traffic_now(),
77038
- sig: log_signature(lvl, text),
77039
- periodic: log_is_periodic(lvl, text),
77040
77023
  hay: (lvl + " " + text).toLowerCase(),
77041
77024
  $node: null
77042
77025
  };
77043
77026
  TRAFFIC_LOG.push(entry);
77044
77027
  if (TRAFFIC_LOG.length > TRAFFIC_MAX) {
77045
77028
  let old = TRAFFIC_LOG.shift();
77046
- if (old.kind !== "log") {
77047
- let c = (TRAFFIC_COUNTS.get(old.sig) || 0) - 1;
77048
- if (c <= 0) TRAFFIC_COUNTS.delete(old.sig);
77049
- else TRAFFIC_COUNTS.set(old.sig, c);
77050
- }
77051
77029
  if (old.$node && old.$node.parentNode) old.$node.parentNode.removeChild(old.$node);
77052
77030
  }
77053
77031
  if (!entry_hidden(entry, build_filter_ctx())) {
@@ -77065,72 +77043,97 @@ function info_log(level, msg, hora) {
77065
77043
  /******************************
77066
77044
  * Trace toggles
77067
77045
  ******************************/
77068
- function trace_traffic() {
77069
- let v = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_traffic"));
77070
- if (v) {
77071
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_inter_event", false);
77072
- v = 0;
77073
- } else {
77074
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_inter_event", true);
77075
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_ievent_callback", info_traffic);
77076
- v = 1;
77077
- }
77078
- (0, _yuneta_gobj_js.kw_set_local_storage_value)("trace_traffic", v);
77046
+ function yuno_trace_command(command, kw) {
77047
+ let r = (0, _yuneta_gobj_js.gobj_command)((0, _yuneta_gobj_js.gobj_yuno)(), command, kw, (0, _yuneta_gobj_js.gobj_yuno)());
77048
+ if (!r || r.result < 0) (0, _yuneta_gobj_js.log_error)(`yui_dev: ${command} ${JSON.stringify(kw)}: ${r ? r.comment : "no answer"}`);
77079
77049
  refresh_dev_chrome();
77080
77050
  }
77081
- function trace_automata() {
77082
- let v = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_automata"));
77083
- if (v === 0) v = 1;
77084
- else if (v === 1) v = 2;
77085
- else v = 0;
77086
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "tracing", v);
77087
- (0, _yuneta_gobj_js.kw_set_local_storage_value)("trace_automata", v);
77088
- refresh_dev_chrome();
77051
+ function global_bit(bit) {
77052
+ return (0, _yuneta_gobj_js.gobj_global_trace_level)() & bit ? 1 : 0;
77089
77053
  }
77090
- function trace_creation() {
77091
- let v = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_creation"));
77092
- v = v === 0 ? 1 : 0;
77093
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_creation", v);
77094
- (0, _yuneta_gobj_js.kw_set_local_storage_value)("trace_creation", v);
77095
- refresh_dev_chrome();
77054
+ var GLOBAL_TRACE_BITS = {
77055
+ "machine": _yuneta_gobj_js.trace_level_t.TRACE_MACHINE,
77056
+ "ev_kw": _yuneta_gobj_js.trace_level_t.TRACE_EV_KW,
77057
+ "create_delete": _yuneta_gobj_js.trace_level_t.TRACE_CREATE_DELETE,
77058
+ "start_stop": _yuneta_gobj_js.trace_level_t.TRACE_START_STOP,
77059
+ "subscriptions": _yuneta_gobj_js.trace_level_t.TRACE_SUBSCRIPTIONS
77060
+ };
77061
+ function toggle_global(level) {
77062
+ yuno_trace_command("set-global-trace", {
77063
+ level,
77064
+ set: global_bit(GLOBAL_TRACE_BITS[level]) ? 0 : 1
77065
+ });
77096
77066
  }
77097
- function trace_start_stop() {
77098
- let v = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_start_stop"));
77099
- v = v === 0 ? 1 : 0;
77100
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_start_stop", v);
77101
- (0, _yuneta_gobj_js.kw_set_local_storage_value)("trace_start_stop", v);
77102
- refresh_dev_chrome();
77067
+ function automata_state() {
77068
+ if (!global_bit(_yuneta_gobj_js.trace_level_t.TRACE_MACHINE)) return 0;
77069
+ return global_bit(_yuneta_gobj_js.trace_level_t.TRACE_EV_KW) ? 2 : 1;
77103
77070
  }
77104
- function trace_subscriptions() {
77105
- let v = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_subscriptions"));
77106
- v = v === 0 ? 1 : 0;
77107
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_subscriptions", v);
77108
- (0, _yuneta_gobj_js.kw_set_local_storage_value)("trace_subscriptions", v);
77109
- refresh_dev_chrome();
77071
+ function toggle_automata() {
77072
+ let v = automata_state();
77073
+ if (v === 0) yuno_trace_command("set-global-trace", {
77074
+ level: "machine",
77075
+ set: 1
77076
+ });
77077
+ else if (v === 1) yuno_trace_command("set-global-trace", {
77078
+ level: "ev_kw",
77079
+ set: 1
77080
+ });
77081
+ else {
77082
+ yuno_trace_command("set-global-trace", {
77083
+ level: "ev_kw",
77084
+ set: 0
77085
+ });
77086
+ yuno_trace_command("set-global-trace", {
77087
+ level: "machine",
77088
+ set: 0
77089
+ });
77090
+ }
77091
+ }
77092
+ function traffic_state() {
77093
+ return (0, _yuneta_gobj_js.gobj_get_gclass_trace_level2)("C_IEVENT_CLI").includes("ievents") ? 1 : 0;
77110
77094
  }
77111
- function trace_i18n() {
77112
- let v = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_i18n"));
77113
- v = v === 0 ? 1 : 0;
77095
+ function toggle_traffic() {
77096
+ yuno_trace_command("set-gclass-trace", {
77097
+ gclass_name: "C_IEVENT_CLI",
77098
+ level: "ievents",
77099
+ set: traffic_state() ? 0 : 1
77100
+ });
77101
+ }
77102
+ function periodic_state() {
77103
+ let on = (0, _yuneta_gobj_js.gobj_global_trace_level)() & _yuneta_gobj_js.trace_level_t.TRACE_TIMER_PERIODIC;
77104
+ let off = (0, _yuneta_gobj_js.gobj_global_trace_no_level)() & _yuneta_gobj_js.trace_level_t.TRACE_TIMER_PERIODIC;
77105
+ return on && !off ? 1 : 0;
77106
+ }
77107
+ function toggle_periodic() {
77108
+ let v = periodic_state() ? 0 : 1;
77109
+ yuno_trace_command("set-global-trace", {
77110
+ level: "timer_periodic",
77111
+ set: v
77112
+ });
77113
+ yuno_trace_command("set-global-no-trace", {
77114
+ level: "timer_periodic",
77115
+ set: v ? 0 : 1
77116
+ });
77117
+ }
77118
+ function i18n_state() {
77119
+ return Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_i18n", 0, false)) ? 1 : 0;
77120
+ }
77121
+ function toggle_i18n() {
77122
+ let v = i18n_state() ? 0 : 1;
77114
77123
  i18next.default.options.debug = v ? true : false;
77115
77124
  (0, _yuneta_gobj_js.kw_set_local_storage_value)("trace_i18n", v);
77116
77125
  refresh_dev_chrome();
77117
77126
  }
77118
- function set_no_poll() {
77119
- let v = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("no_poll"));
77120
- v = v ? 0 : 1;
77121
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "no_poll", v);
77122
- (0, _yuneta_gobj_js.kw_set_local_storage_value)("no_poll", v);
77123
- refresh_dev_chrome();
77124
- }
77125
77127
  /******************************
77126
77128
  * Chrome (controls)
77127
77129
  ******************************/
77128
77130
  function refresh_dev_chrome() {
77129
77131
  document.querySelectorAll(".YDEV_CHIP[data-trace]").forEach(($b) => {
77130
77132
  let key = $b.getAttribute("data-trace");
77133
+ let def = TRACE_DEFS.find((d) => d[0] === key);
77131
77134
  let label = (0, i18next.t)($b.getAttribute("data-label") || "");
77132
- let v = dev_num(key, 0);
77133
- $b.textContent = key === "trace_automata" && v > 0 ? label + " " + v : label;
77135
+ let v = def ? def[3]() : 0;
77136
+ $b.textContent = key === "automata" && v > 0 ? label + " " + v : label;
77134
77137
  $b.classList.toggle("is-active", v > 0);
77135
77138
  });
77136
77139
  let view = dev_view();
@@ -77149,40 +77152,9 @@ function refresh_dev_chrome() {
77149
77152
  document.querySelectorAll(".YDEV_CHIP[data-dir]").forEach(($b) => {
77150
77153
  $b.classList.toggle("is-active", !!dev_num($b.getAttribute("data-dir"), 1));
77151
77154
  });
77152
- document.querySelectorAll(".YDEV_CHIP[data-toggle=\"periodic\"]").forEach(($b) => {
77153
- $b.classList.toggle("is-active", dev_hide_periodic());
77154
- });
77155
77155
  document.querySelectorAll(".YDEV_CHIP[data-toggle=\"automata-simple\"]").forEach(($b) => {
77156
77156
  $b.classList.toggle("is-active", !!dev_simple_mach());
77157
77157
  });
77158
- let $m = document.getElementById("ydev-muted");
77159
- if ($m) {
77160
- $m.replaceChildren();
77161
- let set = dev_muted();
77162
- if (set.size) {
77163
- let $lbl = document.createElement("span");
77164
- $lbl.className = "YDEV_LABEL";
77165
- $lbl.textContent = (0, i18next.t)("muted");
77166
- $lbl.setAttribute("data-i18n", "muted");
77167
- $m.appendChild($lbl);
77168
- set.forEach((sig) => {
77169
- $m.appendChild((0, _yuneta_gobj_js.createElement2)([
77170
- "button",
77171
- {
77172
- class: "YDEV_MUTED_CHIP",
77173
- type: "button",
77174
- title: (0, i18next.t)("unmute"),
77175
- "data-i18n-title": "unmute"
77176
- },
77177
- "⊘ " + sig + " ✕",
77178
- { click: (ev) => {
77179
- ev.stopPropagation();
77180
- unmute_signature(sig);
77181
- } }
77182
- ]));
77183
- });
77184
- }
77185
- }
77186
77158
  update_stats();
77187
77159
  }
77188
77160
  function traffic_to_text() {
@@ -77227,19 +77199,23 @@ function dev_fallback_copy(text) {
77227
77199
  document.body.removeChild(ta);
77228
77200
  }
77229
77201
  function build_control_bar() {
77230
- let trace_chips = TRACE_DEFS.map(([key, label_key, fn]) => [
77202
+ let trace_chips = TRACE_DEFS.map(([key, label_key, title_key, state, toggle]) => [
77231
77203
  "button",
77232
77204
  {
77233
77205
  class: "YDEV_CHIP",
77234
77206
  "data-trace": key,
77235
77207
  "data-label": label_key,
77236
77208
  "data-i18n": label_key,
77237
- type: "button"
77209
+ type: "button",
77210
+ title: (0, i18next.t)(title_key),
77211
+ "data-i18n-title": title_key,
77212
+ "aria-label": (0, i18next.t)(label_key),
77213
+ "data-i18n-aria-label": label_key
77238
77214
  },
77239
77215
  (0, i18next.t)(label_key),
77240
77216
  { click: (ev) => {
77241
77217
  ev.stopPropagation();
77242
- fn();
77218
+ toggle();
77243
77219
  } }
77244
77220
  ]);
77245
77221
  let simple_mach = [
@@ -77377,29 +77353,6 @@ function build_control_bar() {
77377
77353
  mk_dir("in", "⇠", "dev_filter_in", "incoming"),
77378
77354
  mk_dir("err", "⚠", "dev_filter_err", "errors")
77379
77355
  ];
77380
- let periodic_chip = [
77381
- "button",
77382
- {
77383
- class: "YDEV_CHIP",
77384
- "data-toggle": "periodic",
77385
- type: "button",
77386
- title: (0, i18next.t)("hide the timers and the recurring traffic"),
77387
- "data-i18n-title": "hide the timers and the recurring traffic"
77388
- },
77389
- [[
77390
- "span",
77391
- { class: "YDEV_GLYPH" },
77392
- "⊘"
77393
- ], [
77394
- "span",
77395
- { "data-i18n": "periodic" },
77396
- (0, i18next.t)("periodic")
77397
- ]],
77398
- { click: (ev) => {
77399
- ev.stopPropagation();
77400
- toggle_pref("dev_hide_periodic", HIDE_PERIODIC_DEFAULT);
77401
- } }
77402
- ];
77403
77356
  let search = [
77404
77357
  "input",
77405
77358
  {
@@ -77480,7 +77433,7 @@ function build_control_bar() {
77480
77433
  grp("view", [view_seg]),
77481
77434
  expand_grp,
77482
77435
  sep(),
77483
- grp("show", [...dir_chips, periodic_chip]),
77436
+ grp("show", dir_chips),
77484
77437
  sep(),
77485
77438
  grp("find", [search]),
77486
77439
  sep(),
@@ -77515,14 +77468,6 @@ function build_dev_body() {
77515
77468
  { class: "YDEV_BODY" },
77516
77469
  [
77517
77470
  build_control_bar(),
77518
- [
77519
- "div",
77520
- {
77521
- class: "YDEV_MUTED",
77522
- id: "ydev-muted"
77523
- },
77524
- []
77525
- ],
77526
77471
  [
77527
77472
  "div",
77528
77473
  {
@@ -77555,28 +77500,16 @@ function dev_window_was_open() {
77555
77500
  return Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("open_developer_window", 0, false)) ? true : false;
77556
77501
  }
77557
77502
  /************************************************************
77558
- * Apply ALL persisted developer-trace flags to the running
77559
- * yuno. Independent of the dev window — call it once at app
77560
- * startup so a refresh keeps logging whatever was enabled.
77503
+ * Wire the developer window's sinks and the browser-side prefs.
77504
+ * Call it once at app startup.
77505
+ *
77506
+ * The TRACE LEVELS are not here: the yuno restores the ones the user
77507
+ * persisted in its own mt_create (`trace_levels` / `no_trace_levels`),
77508
+ * as a C yuno does. What is left is where the output goes.
77561
77509
  ************************************************************/
77562
77510
  function apply_dev_traces() {
77563
- let traffic = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_traffic", 0, false));
77564
- let trace = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_automata", 0, false));
77565
- let creation = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_creation", 0, false));
77566
- let start_stop = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_start_stop", 0, false));
77567
- let subscriptions = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_subscriptions", 0, false));
77568
- let i18n = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("trace_i18n", 0, false));
77569
- let no_poll = Number((0, _yuneta_gobj_js.kw_get_local_storage_value)("no_poll", 0, false));
77570
- if (traffic) {
77571
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_inter_event", true);
77572
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_ievent_callback", info_traffic);
77573
- } else (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_inter_event", false);
77574
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "tracing", trace);
77575
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_creation", creation);
77576
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_start_stop", start_stop);
77577
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_subscriptions", subscriptions);
77578
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "no_poll", no_poll);
77579
- i18next.default.options.debug = i18n ? true : false;
77511
+ (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_ievent_callback", info_traffic);
77512
+ i18next.default.options.debug = i18n_state() ? true : false;
77580
77513
  (0, _yuneta_gobj_js.gobj_set_trace_machine_format)(dev_simple_mach());
77581
77514
  (0, _yuneta_gobj_js.set_log_callback)(info_log);
77582
77515
  apply_console_route();
@@ -77627,8 +77560,8 @@ function setup_dev(self, show) {
77627
77560
  *
77628
77561
  * Returns { $el, dispose }:
77629
77562
  * - $el: the panel element (control bar + log + stats).
77630
- * - dispose: stops the inter-event traffic trace; call it from
77631
- * the modal's on_close.
77563
+ * - dispose: call it from the modal's on_close (kept for the
77564
+ * contract; the traces outlive the panel).
77632
77565
  ************************************************************/
77633
77566
  function build_dev_panel() {
77634
77567
  ensure_dev_style();
@@ -77649,9 +77582,7 @@ function build_dev_panel() {
77649
77582
  refresh_dev_chrome();
77650
77583
  rerender_all();
77651
77584
  }, 0);
77652
- let dispose = function() {
77653
- (0, _yuneta_gobj_js.gobj_write_attr)((0, _yuneta_gobj_js.gobj_yuno)(), "trace_inter_event", false);
77654
- };
77585
+ let dispose = function() {};
77655
77586
  return {
77656
77587
  $el,
77657
77588
  dispose