@yuneta/gobj-ui 7.23.195 → 7.23.197

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.195",
3
+ "version": "7.23.197",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -19,13 +19,15 @@
19
19
  * fkey, the order of a new sibling, the version that publishes
20
20
  * the change.
21
21
  *
22
- * THE VERSION IS THE POINT, not a detail. A topic whose columns
23
- * changed and whose `topic_version` did not is a topic whose
24
- * persisted `topic_cols.json` masks the whole edit: the restart
25
- * succeeds and nothing moved. Nothing in the store says so, and
26
- * the log that would is on the node. So every write here raises
27
- * the version of the topic it touched, and the toolbar says which
28
- * topics are waiting for a restart.
22
+ * AN EDIT IS A DRAFT. A write here moves no version and reaches no
23
+ * treedb: the host SAVES it (C_TREEDB's save-schema raises the
24
+ * topic_version of what changed and the schema_version, once) and
25
+ * APPLIES it (apply-schema + a restart of the owning yuno). Until
26
+ * the owner's design of M36 (2026-09-21 review) every write here
27
+ * raised both versions, so an edit half made was already the
28
+ * schema of the next start. The topic list marks what this session
29
+ * wrote and has not saved; the host's EV_REFRESH after a save is
30
+ * what forgets it.
29
31
  *
30
32
  * WHAT ELSE IS HERE, and why each is here and not somewhere else:
31
33
  *
@@ -572,21 +574,23 @@ function start_measuring(gobj)
572
574
  }
573
575
 
574
576
  /***************************************************************
575
- * "This topic was written and its version did not move."
577
+ * "This topic was written in this session and is not saved."
576
578
  *
577
- * ONE predicate, because it is asked in two places -- the topic
578
- * list draws a chip with it and the column view draws a banner --
579
- * and two copies of a rule is how they drift.
579
+ * An edit is a DRAFT: it moves no version and reaches no treedb
580
+ * until the host saves it (C_TREEDB's save-schema, which raises the
581
+ * versions of what changed) -- and the save makes the host send
582
+ * EV_REFRESH, whose reload is what forgets the drafts.
583
+ *
584
+ * ONE predicate, because it is asked in three places -- the topic
585
+ * list draws a chip with it, the column view a banner, the export
586
+ * a warning -- and copies of a rule are how they drift.
580
587
  ***************************************************************/
581
- function needs_version_bump(written, baseline, topic)
588
+ function topic_is_draft(written, topic)
582
589
  {
583
- if(!topic || !written || !baseline) {
584
- return false;
585
- }
586
- if(!written[topic.id]) {
590
+ if(!topic || !written) {
587
591
  return false;
588
592
  }
589
- return String(baseline[topic.id]) === String(topic.topic_version);
593
+ return written[topic.id]? true: false;
590
594
  }
591
595
 
592
596
  /***************************************************************
@@ -1041,7 +1045,7 @@ function render_topics(gobj, $body)
1041
1045
  let twice = repeated_names(treedb.topics);
1042
1046
  let $rows = [];
1043
1047
  for(let topic of treedb.topics) {
1044
- let pending = needs_version_bump(priv.written, priv.baseline, topic);
1048
+ let pending = topic_is_draft(priv.written, topic);
1045
1049
  /* Two topics of one name: the id is what tells them apart, and
1046
1050
  * it is what the url has to carry — addressing by name would
1047
1051
  * open the first one whichever row was clicked. */
@@ -1192,7 +1196,7 @@ function render_columns(gobj, $body)
1192
1196
  * The version banner: the one thing that makes
1193
1197
  * an edit succeed and change nothing.
1194
1198
  *----------------------------------------------*/
1195
- let $banner = version_banner(gobj, topic);
1199
+ let $banner = draft_banner(gobj, topic);
1196
1200
  if($banner) {
1197
1201
  $wrap.appendChild($banner);
1198
1202
  }
@@ -1235,48 +1239,34 @@ function render_columns(gobj, $body)
1235
1239
  }
1236
1240
 
1237
1241
  /***************************************************************
1238
- * "This topic changed and its version did not."
1242
+ * "This topic has changes that are not saved."
1239
1243
  *
1240
- * Only shown when it is TRUE — the topic was written in this
1241
- * session and its `topic_version` still reads what it read when
1242
- * the model loaded. Every write this view makes raises it, so
1243
- * the banner is for the case where something else did not: an
1244
- * edit made from the raw tables, or a bump undone by hand.
1244
+ * A draft, not an error: the edit is in __system__ and moves no
1245
+ * version, so the treedb does not see it until the host saves it
1246
+ * (and applies it). The banner says so and offers nothing -- the
1247
+ * save is the host's, one level up, because it publishes the whole
1248
+ * treedb and not one topic.
1245
1249
  ***************************************************************/
1246
- function version_banner(gobj, topic)
1250
+ function draft_banner(gobj, topic)
1247
1251
  {
1248
1252
  let priv = gobj.priv;
1249
1253
 
1250
- if(!needs_version_bump(priv.written, priv.baseline, topic)) {
1254
+ if(!topic_is_draft(priv.written, topic)) {
1251
1255
  return null;
1252
1256
  }
1253
1257
  if(is_readonly(gobj)) {
1254
1258
  return null;
1255
1259
  }
1256
1260
 
1257
- let $banner = createElement2(
1258
- ["div", {class: "SCHEMA_VERSION_BANNER notification is-warning is-light p-3 mb-3 " +
1261
+ return createElement2(
1262
+ ["div", {class: "SCHEMA_DRAFT_BANNER notification is-warning is-light p-3 mb-3 " +
1259
1263
  "is-flex is-align-items-center", style: "gap:.5rem;"}, [
1260
1264
  ["span", {class: "icon"}, [["i", {class: "yi-triangle-exclamation"}]]],
1261
- ["span", {class: "SCHEMA_VERSION_TEXT is-size-7", i18n: "version not raised"},
1262
- t("version not raised")],
1263
- ["button", {class: "SCHEMA_VERSION_BUMP button is-warning",
1264
- type: "button",
1265
- style: "margin-left:auto;",
1266
- title: t("raise the version"), "aria-label": t("raise the version"),
1267
- "data-i18n-title": "raise the version",
1268
- "data-i18n-aria-label": "raise the version"}, [
1269
- ["span", {class: "icon"}, [["i", {class: "yi-circle-up"}]]],
1270
- ["span", {i18n: "raise"}, t("raise")]
1271
- ]]
1265
+ ["span", {class: "SCHEMA_DRAFT_TEXT is-size-7",
1266
+ i18n: "unsaved schema changes: save to publish them"},
1267
+ t("unsaved schema changes: save to publish them")]
1272
1268
  ]]
1273
1269
  );
1274
- let $bump = $banner.querySelector(".SCHEMA_VERSION_BUMP");
1275
- $bump.addEventListener("click", (evt) => {
1276
- evt.stopPropagation();
1277
- gobj_send_event(gobj, "EV_BUMP_VERSION", {topic: topic.name}, gobj);
1278
- });
1279
- return $banner;
1280
1270
  }
1281
1271
 
1282
1272
  /***************************************************************
@@ -2001,6 +1991,8 @@ function open_report(gobj, findings, treedb_id)
2001
1991
  ***************************************************************/
2002
1992
  function open_export(gobj, treedb)
2003
1993
  {
1994
+ let priv = gobj.priv;
1995
+ let drafts = treedb.topics.some((topic) => topic_is_draft(priv.written, topic));
2004
1996
  let c_text = schema_to_c(treedb);
2005
1997
  let json_text = JSON.stringify(schema_to_json(treedb), null, 4);
2006
1998
 
@@ -2017,11 +2009,20 @@ function open_export(gobj, treedb)
2017
2009
  ["p", {class: "SCHEMA_EXPORT_HELP help mb-2",
2018
2010
  i18n: "an edit made here works and lives in no source: this is what to paste back"},
2019
2011
  t("an edit made here works and lives in no source: this is what to paste back")],
2012
+ drafts
2013
+ ? ["p", {class: "SCHEMA_EXPORT_DRAFTS notification is-warning is-light p-2 mb-2 is-size-7",
2014
+ i18n: "unsaved changes: save first, so the literal carries the versions that publish it"},
2015
+ t("unsaved changes: save first, so the literal carries the versions that publish it")]
2016
+ : ["span", {}, ""],
2020
2017
  ["textarea", {class: "SCHEMA_EXPORT_TEXT textarea is-small is-family-monospace",
2021
- rows: "18", readonly: "readonly", spellcheck: "false"}, c_text],
2018
+ rows: "18", readonly: "readonly", spellcheck: "false",
2019
+ title: t("schema"), "data-i18n-title": "schema",
2020
+ "aria-label": t("schema"), "data-i18n-aria-label": "schema"}, c_text],
2022
2021
  ["div", {class: "SCHEMA_EXPORT_ACTIONS is-flex mt-3",
2023
2022
  style: "gap:.5rem; justify-content:flex-end;"}, [
2024
- ["button", {class: "SCHEMA_EXPORT_COPY button", type: "button"}, [
2023
+ ["button", {class: "SCHEMA_EXPORT_COPY button", type: "button",
2024
+ title: t("copy"), "data-i18n-title": "copy",
2025
+ "aria-label": t("copy"), "data-i18n-aria-label": "copy"}, [
2025
2026
  ["span", {class: "icon"}, [["i", {class: "yi-copy"}]]],
2026
2027
  ["span", {i18n: "copy"}, t("copy")]
2027
2028
  ]]
@@ -2240,51 +2241,6 @@ function open_orphans(gobj)
2240
2241
 
2241
2242
 
2242
2243
 
2243
- /***************************************************************
2244
- * THE TWO VERSIONS A SCHEMA EDIT HAS TO RAISE, and why both.
2245
- *
2246
- * `topic_version` is what publishes a change of a topic's
2247
- * columns: leave it and the persisted `topic_cols.json` masks
2248
- * the edit, the restart succeeds and nothing moved.
2249
- *
2250
- * `schema_version` is what publishes the schema as a whole —
2251
- * "the stored one wins on ties, and the incoming one has to be
2252
- * strictly newer to take over" (c_treedb.c). It is safe to
2253
- * raise: re-projection from C compares `c_schema_version`, the
2254
- * version of the LITERAL, precisely so that an edit made here
2255
- * survives every start until a newer literal arrives.
2256
- *
2257
- * So every write that changes a schema carries both, and the
2258
- * operator is never asked to remember either.
2259
- ***************************************************************/
2260
- function version_writes(gobj, topic)
2261
- {
2262
- let treedb = current_treedb(gobj);
2263
- let writes = [];
2264
-
2265
- if(topic) {
2266
- let version = Number(topic.topic_version);
2267
- if(!isFinite(version)) {
2268
- version = 0;
2269
- }
2270
- writes.push({
2271
- op: "update", topic_name: T_TOPICS,
2272
- record: {id: topic.id, topic_version: version + 1}
2273
- });
2274
- }
2275
- if(treedb) {
2276
- let version = Number(treedb.schema_version);
2277
- if(!isFinite(version)) {
2278
- version = 0;
2279
- }
2280
- writes.push({
2281
- op: "update", topic_name: T_TREEDBS,
2282
- record: {id: treedb.id, schema_version: version + 1}
2283
- });
2284
- }
2285
- return writes;
2286
- }
2287
-
2288
2244
  /***************************************************************
2289
2245
  * WHERE THE COLUMNS WERE before this session started moving them.
2290
2246
  *
@@ -2968,7 +2924,7 @@ function ac_save_column(gobj, event, kw, src)
2968
2924
  op: kw.creating ? "create" : "update",
2969
2925
  topic_name: T_COLS,
2970
2926
  record: column_record(gobj, topic, col, values)
2971
- }].concat(version_writes(gobj, topic)));
2927
+ }]);
2972
2928
  }
2973
2929
 
2974
2930
  function ac_delete_column(gobj, event, kw, src)
@@ -3026,7 +2982,7 @@ function ac_move_column(gobj, event, kw, src)
3026
2982
  if(writes.length === 0) {
3027
2983
  return 0; /* dropped where it already was */
3028
2984
  }
3029
- return queue_writes(gobj, writes.concat(version_writes(gobj, topic)));
2985
+ return queue_writes(gobj, writes);
3030
2986
  }
3031
2987
 
3032
2988
  /***************************************************************
@@ -3055,7 +3011,7 @@ function ac_undo_order(gobj, event, kw, src)
3055
3011
  }
3056
3012
  /* Spent: what it named is where the columns are about to be. */
3057
3013
  delete priv.order_undo[topic.id];
3058
- return queue_writes(gobj, writes.concat(version_writes(gobj, topic)));
3014
+ return queue_writes(gobj, writes);
3059
3015
  }
3060
3016
 
3061
3017
  /***************************************************************
@@ -3127,7 +3083,7 @@ function ac_save_topic(gobj, event, kw, src)
3127
3083
  op: kw.creating ? "create" : "update",
3128
3084
  topic_name: T_TOPICS,
3129
3085
  record: topic_record(gobj, treedb, topic, values)
3130
- }].concat(version_writes(gobj, null)));
3086
+ }]);
3131
3087
  }
3132
3088
 
3133
3089
  function ac_delete_topic(gobj, event, kw, src)
@@ -3148,25 +3104,6 @@ function ac_delete_topic(gobj, event, kw, src)
3148
3104
  return 0;
3149
3105
  }
3150
3106
 
3151
- /***************************************************************
3152
- * Raising a version by hand: what the banner offers when
3153
- * something else wrote the topic and left its version alone.
3154
- ***************************************************************/
3155
- function ac_bump_version(gobj, event, kw, src)
3156
- {
3157
- let priv = gobj.priv;
3158
- let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
3159
-
3160
- if(refuse_if_readonly(gobj, event)) {
3161
- return -1;
3162
- }
3163
- if(!topic) {
3164
- log_error(`${gobj_short_name(gobj)}: ${event} names no topic: ${kw.topic}`);
3165
- return -1;
3166
- }
3167
- return queue_writes(gobj, version_writes(gobj, topic));
3168
- }
3169
-
3170
3107
  /***************************************************************
3171
3108
  * Deleting a leftover.
3172
3109
  ***************************************************************/
@@ -3339,7 +3276,7 @@ function ac_confirmed(gobj, event, kw, src)
3339
3276
  return {op: "delete", topic_name: T_COLS, record: {id: col.id}};
3340
3277
  });
3341
3278
  writes.push({op: "delete", topic_name: T_TOPICS, record: {id: topic.id}});
3342
- return queue_writes(gobj, writes.concat(version_writes(gobj, null)));
3279
+ return queue_writes(gobj, writes);
3343
3280
  }
3344
3281
  if(kw.what === "column") {
3345
3282
  let topic = find_topic(priv.model, priv.treedb_id, kw.topic);
@@ -3350,7 +3287,7 @@ function ac_confirmed(gobj, event, kw, src)
3350
3287
  }
3351
3288
  return queue_writes(gobj, [{
3352
3289
  op: "delete", topic_name: T_COLS, record: {id: col.id}
3353
- }].concat(version_writes(gobj, topic)));
3290
+ }]);
3354
3291
  }
3355
3292
 
3356
3293
  log_error(`${gobj_short_name(gobj)}: ${event} of nothing this view asked about: ${kw.what}`);
@@ -3450,8 +3387,7 @@ function create_gclass(gclass_name)
3450
3387
  ["EV_DELETE_COLUMN", ac_delete_column, null],
3451
3388
  ["EV_SAVE_COLUMN", ac_save_column, null],
3452
3389
  ["EV_MOVE_COLUMN", ac_move_column, null],
3453
- ["EV_UNDO_ORDER", ac_undo_order, null],
3454
- ["EV_BUMP_VERSION", ac_bump_version, null]
3390
+ ["EV_UNDO_ORDER", ac_undo_order, null]
3455
3391
  ])],
3456
3392
 
3457
3393
  /* A write, or a plan of them, in flight. Only the answers and
@@ -3494,7 +3430,6 @@ function create_gclass(gclass_name)
3494
3430
  ["EV_SAVE_COLUMN", 0],
3495
3431
  ["EV_MOVE_COLUMN", 0],
3496
3432
  ["EV_UNDO_ORDER", 0],
3497
- ["EV_BUMP_VERSION", 0],
3498
3433
  ["EV_VALIDATE", 0],
3499
3434
  ["EV_EXPORT", 0],
3500
3435
  ["EV_IMPORT", 0],
@@ -3549,5 +3484,5 @@ export {
3549
3484
  register_c_yui_schema_editor,
3550
3485
  /* Exported for its test: the rule that decides whether a topic still
3551
3486
  * owes a version bump, which is asked in two places. */
3552
- needs_version_bump
3487
+ topic_is_draft
3553
3488
  };
@@ -19,13 +19,19 @@
19
19
  * Two outputs, because they answer different questions:
20
20
  * schema_to_json() — the schema as a value: to diff, to keep, to
21
21
  * feed back in.
22
- * schema_to_c() — the same value as the literal: single
23
- * quotes, `\n\` continuations, padded, ready
24
- * to paste.
22
+ * schema_to_c() — the same value as the WHOLE file
23
+ * `treedb_schema_<db>.c`: the graph of the
24
+ * schema as a comment (schema_to_diagram()),
25
+ * then the literal — single quotes, `\n\`
26
+ * continuations, padded. The file holds that
27
+ * and nothing else, so an export replaces it
28
+ * whole. json_to_c() does the same from the
29
+ * literal's own JSON.
25
30
  *
26
31
  * Copyright (c) 2026, ArtGins.
27
32
  * All Rights Reserved.
28
33
  ***********************************************************************/
34
+ import { schema_to_diagram } from "./schema_to_diagram.js";
29
35
  import {
30
36
  col_flags,
31
37
  col_hook,
@@ -326,15 +332,25 @@ function c_pair(lines, key, value, indent, trailing)
326
332
  /***************************************************************
327
333
  * schema_to_c(treedb, options) -> the C source text
328
334
  *
329
- * options {var_name, pad}
330
- * var_name the array's name; defaults to
331
- * `treedb_schema_<treedb id without its prefix>`
332
- * pad column of the `\n\` continuation
335
+ * options {var_name, pad, with_diagram}
336
+ * var_name the array's name; defaults to
337
+ * `treedb_schema_<treedb id without its prefix>`
338
+ * pad column of the `\n\` continuation
339
+ * with_diagram the graph as a comment above the array
340
+ * (default true)
333
341
  ***************************************************************/
334
342
  function schema_to_c(treedb, options)
343
+ {
344
+ return json_to_c(schema_to_json(treedb), options);
345
+ }
346
+
347
+ /***************************************************************
348
+ * json_to_c(json, options) -> the C source text, from the schema
349
+ * as its literal holds it. Same options as schema_to_c().
350
+ ***************************************************************/
351
+ function json_to_c(json, options)
335
352
  {
336
353
  let opts = options || {};
337
- let json = schema_to_json(treedb);
338
354
 
339
355
  if(!json) {
340
356
  return "";
@@ -368,7 +384,30 @@ function schema_to_c(treedb, options)
368
384
  return `${line}${padding}\\n\\`;
369
385
  });
370
386
 
371
- return `static char ${name}[]= "\\\n${body.join("\n")}\n";\n`;
387
+ let literal = `static char ${name}[]= "\\\n${body.join("\n")}\n";\n`;
388
+ if(opts.with_diagram === false) {
389
+ return literal;
390
+ }
391
+ return c_diagram_comment(schema_to_diagram(json)) + "\n" + literal;
392
+ }
393
+
394
+ /***************************************************************
395
+ * The diagram as the comment that heads the file.
396
+ ***************************************************************/
397
+ function c_diagram_comment(diagram)
398
+ {
399
+ let lines = [
400
+ "/*",
401
+ " Generated from the literal below by schema_to_diagram() (gobj-ui).",
402
+ " This file holds this comment and the literal, nothing else: replace",
403
+ " it whole with an export of the schema editor.",
404
+ "",
405
+ ];
406
+ for(let line of diagram.split("\n")) {
407
+ lines.push(line ? " " + line : "");
408
+ }
409
+ lines.push("*/");
410
+ return lines.join("\n") + "\n";
372
411
  }
373
412
 
374
413
 
@@ -378,4 +417,5 @@ export {
378
417
  COL_KEY_ORDER,
379
418
  schema_to_json,
380
419
  schema_to_c,
420
+ json_to_c,
381
421
  };
@@ -12,7 +12,8 @@
12
12
  ***********************************************************************/
13
13
  import { describe, test, expect } from "vitest";
14
14
  import { build_schema_model } from "./schema_model.js";
15
- import { schema_to_json, schema_to_c } from "./schema_to_c.js";
15
+ import { schema_to_json, schema_to_c, json_to_c } from "./schema_to_c.js";
16
+ import { schema_to_diagram } from "./schema_to_diagram.js";
16
17
 
17
18
 
18
19
  /***************************************************************
@@ -134,7 +135,24 @@ describe("schema_to_json", () => {
134
135
  });
135
136
 
136
137
  describe("schema_to_c — the round trip", () => {
137
- const source = schema_to_c(TREEDB);
138
+ const file = schema_to_c(TREEDB);
139
+ const source = file.slice(file.indexOf("static char"));
140
+
141
+ test("the file is the diagram as a comment, then the literal, nothing else", () => {
142
+ expect(file.startsWith("/*\n")).toBe(true);
143
+ const close = file.indexOf("\n*/\n");
144
+ expect(close).toBeGreaterThan(0);
145
+ expect(file.slice(close + 4)).toBe("\n" + source);
146
+ expect(file).toContain(schema_to_diagram(schema_to_json(TREEDB)).split("\n")[0]);
147
+ });
148
+
149
+ test("without the diagram it is the literal alone", () => {
150
+ expect(schema_to_c(TREEDB, {with_diagram: false})).toBe(source);
151
+ });
152
+
153
+ test("json_to_c() writes the same file from the literal's JSON", () => {
154
+ expect(json_to_c(schema_to_json(TREEDB))).toBe(file);
155
+ });
138
156
 
139
157
  test("it is a C array declaration with the conventional name", () => {
140
158
  expect(source.startsWith("static char treedb_schema_sample[]= \"\\\n")).toBe(true);
@@ -153,7 +171,7 @@ describe("schema_to_c — the round trip", () => {
153
171
  });
154
172
 
155
173
  test("the caller can name the array and set the continuation column", () => {
156
- const custom = schema_to_c(TREEDB, {var_name: "my_schema", pad: 40});
174
+ const custom = schema_to_c(TREEDB, {var_name: "my_schema", pad: 40, with_diagram: false});
157
175
  expect(custom.startsWith("static char my_schema[]=")).toBe(true);
158
176
  expect(load_like_the_yuno(custom)).toEqual(schema_to_json(TREEDB));
159
177
  });