strom-research 1.26.0-beta.1 → 1.26.0-beta.2

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/CHANGELOG.cs.md CHANGED
@@ -7,7 +7,7 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
7
7
 
8
8
  ## 1.26.0
9
9
 
10
- *Připravuje se: bety do 1.26.0-beta.1. Oddíl bude úplný s vydáním.*
10
+ *Připravuje se: bety do 1.26.0-beta.2. Oddíl bude úplný s vydáním.*
11
11
 
12
12
  - **Brána týdne předplatného už neukončí běh kvůli jedné nepřečtené odpovědi.** Zeptá se znovu, pak čtvrt hodiny počká
13
13
  a řekne, co dostala. Nepřihlášený agent se ohlásí už před prvním sezením i s postupem přihlášení; výchozí složka
@@ -21,6 +21,13 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
21
21
  vrátit. Souhrn sezení o místech začíná tím, co se udělalo – ztotožněno, ověřeno, s otázkou, nedohledáno – a jedno
22
22
  sezení vezme víc míst sousedních oblastí. Z „v Lhotě v čp. 12“ už nezůstane místo „v Lhotě v“:
23
23
  místem je obec, jak ji výzkum píše jinde, i když ji věta skloňuje. Každý název se do záznamu místa zapíše jen jednou.
24
+ - **Fronta míst se po vyřízení míst vyprázdní.** Místo ztotožněné, ověřené, předané k otázce nebo nedohledané odejde z
25
+ fronty se všemi svými tvary, i s tím, který fakta mezitím píší jinak; místa, která sezení už udělalo, se znovu neberou
26
+ a počet čekajících sedí. Co ve frontě nechala dřívější verze, se odebere samo (jen vyřízená místa, krok jde vrátit);
27
+ kontrola vyžádaná u místa, které čeká na ztotožnění, zůstane jako kontrola. Každý krok nad místy jde vrátit vlastním
28
+ číslem, i když čeká jiný (také odpovědi člověka). Chybné jméno v záznamu místa — překlep ze záznamu, který už žádný
29
+ fakt nepíše, jméno jiného místa — jde odebrat i s důvodem (`strom place names L… --drop`); sezení nad místy vidí další
30
+ jména záznamu s jazykem a roky; kontrola může uvést, o jaký zdroj se opřela; příslušnost jde zadat i k jednomu roku.
24
31
  - V rozporech úpravy z aplikace už číslo domu, které značku má, nedostane značku podruhé (polsky „nr domu nr domu 12“),
25
32
  a pomlčka v titulku je podle jazyka výzkumu.
26
33
 
package/CHANGELOG.de.md CHANGED
@@ -7,7 +7,7 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
7
7
 
8
8
  ## 1.26.0
9
9
 
10
- *In Vorbereitung: die Betas bis 1.26.0-beta.1. Der Abschnitt ist mit dem Release vollständig.*
10
+ *In Vorbereitung: die Betas bis 1.26.0-beta.2. Der Abschnitt ist mit dem Release vollständig.*
11
11
 
12
12
  - **Das Tor der Abo-Woche beendet einen Lauf nicht mehr wegen einer unlesbaren Antwort.** Es fragt erneut, wartet dann
13
13
  eine Viertelstunde und nennt, was es erhielt. Ein nicht angemeldeter Agent wird schon vor der ersten Sitzung gemeldet,
@@ -25,6 +25,16 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
25
25
  benachbarter Gebiete. Aus „in Lhota in Nr. 12“ bleibt kein Ort „in Lhota in“ mehr:
26
26
  Ort ist die Gemeinde, wie die Forschung sie sonst schreibt, auch wenn der Satz sie beugt. Jeder Name steht nur einmal
27
27
  im Eintrag eines Ortes.
28
+ - **Die Warteschlange der Orte leert sich, wenn die Orte erledigt sind.** Ein Ort, der bestimmt, geprüft, erfragt oder
29
+ nicht gefunden wurde, verlässt die Warteschlange mit allen seinen Formen, auch mit einer, die die Fakten inzwischen
30
+ anders schreiben; Orte, die eine Sitzung schon erledigt hat, werden nicht erneut genommen, und die Zahl des Wartenden
31
+ stimmt. Was eine frühere Version in der Warteschlange ließ, wird von selbst entfernt (nur erledigte Orte, der Schritt
32
+ lässt sich rückgängig machen); eine Prüfung, die für einen Ort verlangt wird, der auf die Bestimmung wartet, bleibt
33
+ als Prüfung. Jeder Schritt bei den Orten lässt sich mit seiner eigenen Nummer rückgängig machen, auch wenn ein anderer
34
+ wartet (auch die Antworten der Person). Ein falscher Name im Datensatz eines Ortes — ein Versehen eines Eintrags, das
35
+ kein Faktum mehr schreibt, der Name eines anderen Ortes — lässt sich mit Begründung entfernen (`strom place names L…
36
+ --drop`); die Sitzung über die Orte sieht die weiteren Namen des Datensatzes mit Sprache und Jahren; eine Prüfung kann
37
+ die Quelle nennen, auf die sie sich stützt; eine Zugehörigkeit lässt sich auch für ein einzelnes Jahr angeben.
28
38
  - In den Widersprüchen einer Änderung aus der App bekommt eine Hausnummer, die ihre Bezeichnung schon trägt, sie nicht
29
39
  ein zweites Mal (polnisch „nr domu nr domu 12“), und der Gedankenstrich im Titel richtet sich nach der Sprache der
30
40
  Forschung.
package/CHANGELOG.md CHANGED
@@ -7,7 +7,7 @@ The changes of each version in short. The full notes and the install lines are o
7
7
 
8
8
  ## 1.26.0
9
9
 
10
- *In preparation: the betas up to 1.26.0-beta.1. The section is complete with the release.*
10
+ *In preparation: the betas up to 1.26.0-beta.2. The section is complete with the release.*
11
11
 
12
12
  - **The gate of the subscription's week no longer ends a run over one answer it cannot read.** It asks again, then
13
13
  waits a quarter of an hour and says what it got. An agent not logged in is said before the first session, with how to
@@ -23,6 +23,14 @@ The changes of each version in short. The full notes and the install lines are o
23
23
  located — and takes more places of neighbouring areas at once. "in Lhota in no. 12" no longer leaves "in Lhota in" as
24
24
  a place: the place is the municipality as the research writes it elsewhere, also where the sentence declined it. Each
25
25
  name goes into a place's record once.
26
+ - **The queue of places empties as the places are done.** A place identified, checked, asked about or not located leaves
27
+ the queue with every form of it, also one the facts write otherwise since; the places a session already did are no
28
+ longer taken again, and the count of what waits is right. What an earlier version left in the queue is taken out by
29
+ itself (only places dealt with, the step can be undone); a check asked of a place waiting to be identified is kept as
30
+ the check. Each step on places is undone by its own number, also while another waits (the person's answers among
31
+ them). A wrong name of a place's record — a slip of a record no fact writes any more, another place's name — can be
32
+ taken off with the reason (`strom place names L… --drop`); the session on the places sees the record's other names
33
+ with their language and years; a check can name the reference it stood on; a jurisdiction can be given for one year.
26
34
  - In the conflicts of an edit from the app a house that says it is one already no longer gets its marker twice ("nr domu nr
27
35
  domu 12" in Polish), and the title's dash is the one of the research's language.
28
36
 
package/CHANGELOG.pl.md CHANGED
@@ -7,7 +7,7 @@ Zmiany każdej wersji w skrócie. Pełne informacje o wydaniach i polecenia inst
7
7
 
8
8
  ## 1.26.0
9
9
 
10
- *W przygotowaniu: bety do 1.26.0-beta.1. Sekcja będzie kompletna z wydaniem.*
10
+ *W przygotowaniu: bety do 1.26.0-beta.2. Sekcja będzie kompletna z wydaniem.*
11
11
 
12
12
  - **Warunek tygodnia subskrypcji nie kończy już przebiegu z powodu jednej nieodczytanej odpowiedzi.** Pyta ponownie,
13
13
  potem czeka kwadrans i podaje, co otrzymał. Niezalogowany agent jest zgłaszany już przed pierwszą sesją, ze sposobem
@@ -23,6 +23,15 @@ Zmiany każdej wersji w skrócie. Pełne informacje o wydaniach i polecenia inst
23
23
  odnaleziono – a jedna sesja bierze więcej miejsc z sąsiednich obszarów. Z „w Lhocie w nr 12” nie zostaje już
24
24
  miejsce „w Lhocie w”: miejscem jest miejscowość tak, jak badanie zapisuje ją gdzie indziej, także gdy zdanie ją
25
25
  odmienia. Każda nazwa trafia do rekordu miejsca tylko raz.
26
+ - **Kolejka miejsc opróżnia się, gdy miejsca są załatwione.** Miejsce zidentyfikowane, sprawdzone, przekazane jako
27
+ pytanie albo nieustalone opuszcza kolejkę ze wszystkimi swoimi formami, także z tą, którą fakty zapisują teraz
28
+ inaczej; miejsca, które sesja już zrobiła, nie są brane ponownie, a liczba oczekujących się zgadza. To, co w kolejce
29
+ zostawiła wcześniejsza wersja, usuwa się samo (tylko załatwione miejsca, krok można cofnąć); sprawdzenie zlecone dla
30
+ miejsca czekającego na identyfikację zostaje jako sprawdzenie. Każdy krok przy miejscach można cofnąć jego własnym
31
+ numerem, także gdy czeka inny (również odpowiedzi osoby). Błędną nazwę w rekordzie miejsca — pomyłkę z zapisu, której
32
+ żaden fakt już nie zapisuje, nazwę innego miejsca — można usunąć z uzasadnieniem (`strom place names L… --drop`);
33
+ sesja nad miejscami widzi pozostałe nazwy rekordu z językiem i latami; sprawdzenie może podać źródło, na którym się
34
+ oparło; przynależność można podać także dla jednego roku.
26
35
  - W sprzecznościach zmian z aplikacji numer domu, który ma już oznaczenie, nie dostaje go po raz drugi („nr domu nr
27
36
  domu 12”), a myślnik w tytule zależy od języka badania.
28
37
 
@@ -5,7 +5,7 @@
5
5
  import { langName } from "../core/lang.js";
6
6
  import { match } from "../cli/registry.js";
7
7
  import { synopsis } from "./sheet.js";
8
- import { factForms, personsHand, PLACES_IDENTIFY_ORIGIN, queueBatches, readPlacesFile, recordIndex } from "../core/placeid.js";
8
+ import { factForms, otherNames, personsHand, PLACES_IDENTIFY_ORIGIN, queueBatches, readPlacesFile, recordIndex } from "../core/placeid.js";
9
9
  import { historyOn } from "../core/pastevents.js";
10
10
  import { othersRecords } from "../core/placeadopt.js";
11
11
  import { foldText } from "../core/text.js";
@@ -24,6 +24,24 @@ function span(ys) {
24
24
  const b = Math.max(...ys);
25
25
  return a === b ? String(a) : `${a}–${b}`;
26
26
  }
27
+ /** A name as the agent writes it: name[@lang][:from-to]. */
28
+ function nameSaid(n) {
29
+ const years = n.from === undefined && n.to === undefined ? "" : n.from === n.to ? `:${n.from}` : `:${n.from ?? ""}-${n.to ?? ""}`;
30
+ return `${n.name}${n.lang ? `@${n.lang}` : ""}${years}`;
31
+ }
32
+ /** A record's names: today's, its other names (another language, old names — with their language and years), the forms of the facts. */
33
+ function recordNames(p) {
34
+ const rest = otherNames(p);
35
+ const alt = rest.filter((n) => n.kind !== "form");
36
+ const forms = rest.filter((n) => n.kind === "form");
37
+ return [
38
+ nameSaid(p.names[0]),
39
+ alt.length ? `other names ${alt.map(nameSaid).join(", ")}` : "",
40
+ forms.length ? `forms ${forms.map((n) => n.name).join(", ")}` : "",
41
+ ]
42
+ .filter(Boolean)
43
+ .join("; ");
44
+ }
27
45
  /** One place of the batch: its form, the facts, the people, the archives of its sources, the record of it. */
28
46
  function placeLine(tree, form, f, entry, p, others) {
29
47
  const areas = f
@@ -40,7 +58,7 @@ function placeLine(tree, form, f, entry, p, others) {
40
58
  f ? `${f.facts} fact${f.facts === 1 ? "" : "s"} ${span(f.years)}` : "",
41
59
  persons.length ? `people ${persons.join(" ")}${f.persons.size > persons.length ? " …" : ""}` : "",
42
60
  areas.length ? `sources from ${areas.join(", ")}` : "",
43
- p ? `record ${p.id}: ${p.names.map((n) => n.name).join(", ")}` : "no record yet",
61
+ p ? `record ${p.id}: ${recordNames(p)}` : "no record yet",
44
62
  p?.parent ? `in ${p.parent}` : "",
45
63
  p?.jurisdictions.length ? p.jurisdictions.map((j) => `${j.kind} ${j.name}${j.from || j.to ? ` ${j.from ?? "…"}–${j.to ?? "…"}` : ""}`).join(", ") : "",
46
64
  p?.coords ? `on the map ${p.coords.lat.toFixed(4)}, ${p.coords.lon.toFixed(4)}${personsHand(p) ? " (the person's — never moved)" : ""}` : "",
@@ -89,8 +107,11 @@ export function placesIdentifySections(tree, task, alone, settings) {
89
107
  "",
90
108
  "The record of a place:",
91
109
  "- its first name is the official name of today in the language of the country it lies in now (a village now in Poland: Polish; the German name is historical) — the names of the facts and the old names go in as --form and --alt name@lang:from-to (an old name: from-to its years; one year: name@lang:1844)",
110
+ "- the record's other names above are known already (name@language:years): today's name in another language may be among them — look it up by that name before calling it not found, and set it first with --name when the reference confirms it",
111
+ "- a name of the record found wrong (another place's name, a slip of a record no fact writes any more): strom place names L… --drop \"<name>\" --why \"what shows it is wrong\" — never left in with a note that it is wrong",
92
112
  "- what is found of a place goes into its record, also where the coordinates are right already: strom place verify L… --ok --why \"…\" --alt … --form … (never only into the history)",
93
- "- a jurisdiction found wrong (another parish): strom place verify L… --ok --why \"what the records say\" --drop-jurisdiction parish:<name> --jurisdiction parish:<right one>:from-to",
113
+ "- a jurisdiction found wrong (another parish): strom place verify L… --ok --why \"what the records say\" --drop-jurisdiction parish:<name> --jurisdiction parish:<right one>:from-to (one year: parish:<name>:1899)",
114
+ "- the reference a check stood on: strom place verify L… --ok --why \"…\" --source-title \"…\" --source-url …",
94
115
  "- a form of the facts that is a place with a record already (\"Nové Lhoty\" of Lhoty): strom place identify L… --form \"<form>\" — never a second record; two records of one place: strom place merge L… --into L… --reason \"…\"",
95
116
  "",
96
117
  "Three degrees, never guessed:",
@@ -115,6 +136,7 @@ export function placesIdentifySections(tree, task, alone, settings) {
115
136
  cmd(["place", "verify"]),
116
137
  cmd(["place", "rewrite"]),
117
138
  cmd(["place", "merge"]),
139
+ cmd(["place", "names"]),
118
140
  cmd(["place", "show"]),
119
141
  ...(history ? [cmd(["history", "map"])] : []),
120
142
  ].filter(Boolean);
@@ -8,7 +8,7 @@ import { UsageError } from "../core/errors.js";
8
8
  import { listOpt } from "../core/records.js";
9
9
  import { refuseInArchive } from "../core/state.js";
10
10
  import { isAgent } from "../core/which.js";
11
- import { answerPlaces, cancelPlaces, factForms, identifyEstimate, identifyPlace, parseCandidate, parseJurisdiction, PLACE_TAKES, placeState, proposePlace, queueBatches, queueOrder, queuePlaces, raisePlacesTask, readPlacesFile, recordIndex, rewriteForm, undoPlaces, unlocatePlace, verifyPlace, identifyingNow, mergePlaces, parseAltName, parseDrop, placeIdOf, PLACE_FLAGS, REQUEST_MAX, } from "../core/placeid.js";
11
+ import { answerPlaces, cancelPlaces, dropPlaceNames, factForms, identifyEstimate, identifyPlace, parseCandidate, parseJurisdiction, PLACE_TAKES, placeState, proposePlace, queueBatches, queueOrder, queuePlaces, raisePlacesTask, readPlacesFile, recordIndex, rewriteForm, undoPlaces, unlocatePlace, verifyPlace, identifyingNow, mergePlaces, parseAltName, parseDrop, placeIdOf, PLACE_FLAGS, REQUEST_MAX, } from "../core/placeid.js";
12
12
  import { actsAsArchive } from "../core/state.js";
13
13
  function str(v) {
14
14
  return typeof v === "string" && v.trim() ? v.trim() : undefined;
@@ -24,7 +24,7 @@ const DETAIL_OPTIONS = [
24
24
  { name: "form", type: "string", multiple: true, value: "<place>", description: "another form of the facts that is the same place (kept among the record's names as a form, out of the queue; repeatable)" },
25
25
  { name: "kind", type: "string", value: "<kind>", description: "village, town, part of a municipality, …" },
26
26
  { name: "parent", type: "string", value: "<L…>", description: "the place it is part of" },
27
- { name: "jurisdiction", type: "string", multiple: true, value: "<kind:name[:from-to]>", description: 'e.g. "parish:Lhotov:1784-" or "district:Lhotov" (repeatable)' },
27
+ { name: "jurisdiction", type: "string", multiple: true, value: "<kind:name[:from-to]>", description: 'e.g. "parish:Lhotov:1784-", "district:Lhotov" or one year "district:Lhotov:1899" (repeatable)' },
28
28
  { name: "drop-jurisdiction", type: "string", multiple: true, value: "<kind:name>", description: "a jurisdiction of the record found wrong: taken off, --why says why (repeatable)" },
29
29
  ];
30
30
  register({
@@ -121,6 +121,8 @@ register({
121
121
  { name: "ok", type: "boolean", description: "the records agree with the coordinates" },
122
122
  { name: "undecided", type: "string", value: "<why>", description: "it cannot be decided, and why" },
123
123
  { name: "why", type: "string", value: "<text>", description: "with --ok: on what (kept with the check); with --drop-jurisdiction: why it is wrong" },
124
+ { name: "source-title", type: "string", value: "<title>", description: "the reference the check stands on (a gazetteer, a register of places)" },
125
+ { name: "source-url", type: "string", value: "<url>", description: "its address" },
124
126
  ...DETAIL_OPTIONS,
125
127
  ],
126
128
  args: [{ name: "place", description: "place ID (L0012) or the place as the facts write it", required: true }],
@@ -134,7 +136,12 @@ register({
134
136
  refuseInArchive(tree, "place verify", ctx.settings.agent(tree.config).value);
135
137
  if (!!opts.ok === !!opts.undecided)
136
138
  throw new UsageError("--ok or --undecided \"<why>\"", { hint: `strom place verify ${args[0] ?? "L…"} --ok --why "on what" (names found: --alt, --form)` });
137
- const r = verifyPlace(tree, args[0], opts.ok ? { ok: true, ...(str(opts.why) ? { why: str(opts.why) } : {}) } : { ok: false, why: String(opts.undecided) }, detailsOf(opts));
139
+ const title = str(opts["source-title"]);
140
+ const url = str(opts["source-url"]);
141
+ if (url && !title)
142
+ throw new UsageError("--source-url with its --source-title");
143
+ const source = title ? { source: { title, ...(url ? { url } : {}) } } : {};
144
+ const r = verifyPlace(tree, args[0], opts.ok ? { ok: true, ...(str(opts.why) ? { why: str(opts.why) } : {}), ...source } : { ok: false, why: String(opts.undecided), ...source }, detailsOf(opts));
138
145
  raisePlacesTask(tree, actsAsArchive(tree));
139
146
  return stepResult(tree, r);
140
147
  },
@@ -185,11 +192,35 @@ register({
185
192
  propose: !!opts.propose,
186
193
  ...(str(opts.why) ? { why: str(opts.why) } : {}),
187
194
  });
195
+ raisePlacesTask(tree, actsAsArchive(tree));
188
196
  return {
189
197
  text: lines(written(tree), r.left ? `left: ${counted(r.left, "fact")} (their house says another thing — never written over)` : undefined),
190
198
  data: { place: r.place, changed: r.changed, left: r.left, asked: r.asked },
191
199
  };
192
200
  },
201
+ }, {
202
+ path: ["place", "names"],
203
+ summary: "Take a wrong name off a place record (a slip of a record kept as a form, another place's name), with the reason",
204
+ group: "sources",
205
+ tree: true,
206
+ writes: true,
207
+ description: "Never today's name (the first: --name of strom place verify or identify sets another) and never a name a fact still writes for the place (strom place rewrite first). Other names are added with --alt and --form of strom place identify or verify.",
208
+ args: [{ name: "place", description: "place ID (L0012)", required: true }],
209
+ options: [
210
+ { name: "drop", type: "string", multiple: true, value: "<name>", description: "the name to take off, as the record has it" },
211
+ { name: "why", type: "string", value: "<text>", description: "why it is wrong (kept with the step)" },
212
+ ],
213
+ examples: ['strom place names L0002 --drop "v Lhotě v" --why "a slip of the register; the facts write Lhota now"'],
214
+ run(ctx, { args, opts }) {
215
+ const tree = ctx.tree();
216
+ refuseInArchive(tree, "place names", ctx.settings.agent(tree.config).value);
217
+ const r = dropPlaceNames(tree, args[0], listOpt(opts.drop), String(opts.why ?? ""));
218
+ raisePlacesTask(tree, actsAsArchive(tree));
219
+ return {
220
+ text: lines(`${r.place.id}: ${r.dropped.map((n) => n.name).join(", ")} taken off`, `undo (the person's): strom place undo ${r.undo}`, written(tree)),
221
+ data: { place: r.place, dropped: r.dropped, undo: r.undo },
222
+ };
223
+ },
193
224
  }, {
194
225
  path: ["place", "queue"],
195
226
  summary: "Queue places to be identified (or their coordinates checked) — or show the queue",
@@ -304,12 +335,12 @@ register({
304
335
  },
305
336
  }, {
306
337
  path: ["place", "undo"],
307
- summary: "Undo the person's last step on places (answers)",
338
+ summary: "Undo a step on places by its id (the person's answers, names taken off, the queue tidied)",
308
339
  group: "sources",
309
340
  tree: true,
310
341
  writes: true,
311
- person: "Undoing the person's answers is the person's own",
312
- args: [{ name: "step", description: "the step's id (the answer prints it)", required: true }],
342
+ person: "Undoing a step on places is the person's own",
343
+ args: [{ name: "step", description: "the step's id (the step prints it; each of the last 20 steps once)", required: true }],
313
344
  examples: ["strom place undo u1a2b3c"],
314
345
  run(ctx, { args }) {
315
346
  const tree = ctx.tree();
@@ -41,12 +41,30 @@ export function readPlacesFile(tree) {
41
41
  for (const [k, v] of Object.entries(f?.queue ?? {}))
42
42
  if (v && typeof v.form === "string" && v.form.trim())
43
43
  queue[k] = v;
44
- return { queue, ...(f?.undo ? { undo: f.undo } : {}), ...(f?.converted ? { converted: f.converted } : {}), ...(typeof f?.repaired === "number" ? { repaired: f.repaired } : {}) };
44
+ const undos = Array.isArray(f?.undos) ? f.undos.filter((u) => u && typeof u.id === "string") : [];
45
+ return { queue, ...(f?.undo ? { undo: f.undo } : {}), ...(undos.length ? { undos } : {}), ...(f?.converted ? { converted: f.converted } : {}), ...(typeof f?.repaired === "number" ? { repaired: f.repaired } : {}) };
45
46
  }
46
47
  catch {
47
48
  return { queue: {} };
48
49
  }
49
50
  }
51
+ /** How many steps on places can be undone, each by its own ID (the oldest go first). */
52
+ export const UNDO_KEPT = 20;
53
+ /** A step to be undone kept: the newest; the one before it waits among the others, never lost to it. */
54
+ function keepUndo(file, u) {
55
+ if (file.undo)
56
+ file.undos = [...(file.undos ?? []), file.undo].slice(-(UNDO_KEPT - 1));
57
+ file.undo = u;
58
+ return u.id;
59
+ }
60
+ /** A new step's undo ID: never one waiting already (two steps in one millisecond). */
61
+ function undoId(file) {
62
+ const taken = new Set([file.undo?.id, ...(file.undos ?? []).map((u) => u.id)]);
63
+ let t = Date.now();
64
+ while (taken.has(`u${t.toString(36)}`))
65
+ t++;
66
+ return `u${t.toString(36)}`;
67
+ }
50
68
  function fileContent(f) {
51
69
  return stringifyCanonical(f);
52
70
  }
@@ -96,8 +114,12 @@ export function factForms(tree) {
96
114
  * okres Horní"); none when two do — a place two records could be belongs to none until its writing is set right.
97
115
  */
98
116
  export function recordIndex(tree) {
117
+ return recordIndexOf(tree.list("place"));
118
+ }
119
+ /** recordIndex over the given records (the records as a step leaves them). */
120
+ function recordIndexOf(places) {
99
121
  const byName = new Map();
100
- for (const p of tree.list("place")) {
122
+ for (const p of places) {
101
123
  if (p.retracted)
102
124
  continue;
103
125
  for (const k of new Set(p.names.map((n) => foldText(n.name))))
@@ -297,7 +319,17 @@ export function queuePlaces(tree, req, by = "person") {
297
319
  skipped.push({ form: w.form, why: "running" });
298
320
  continue;
299
321
  }
300
- if ([w.key, ...others].some((k) => file.queue[k])) {
322
+ const had = [w.key, ...others].find((k) => file.queue[k]);
323
+ if (had) {
324
+ const e = file.queue[had];
325
+ // queued to be looked for, and on the map since: the check asked now takes its place (the tidying would take
326
+ // the old entry out as identified, and the person's check with it)
327
+ if (req.check && w.place?.coords && !e.check) {
328
+ const { back: _b, ...rest } = e;
329
+ file.queue[had] = { ...rest, at, by, check: true };
330
+ queued.push(e.form);
331
+ continue;
332
+ }
301
333
  skipped.push({ form: w.form, why: "queued" });
302
334
  continue;
303
335
  }
@@ -512,7 +544,7 @@ function setFirst(p, today, forms) {
512
544
  }
513
545
  /** "<kind>:<name>" of a jurisdiction to take off. */
514
546
  export function parseDrop(s) {
515
- const m = /^([^:]+):(.+?)(?::\d{0,4}-\d{0,4})?$/u.exec(s.normalize("NFC").trim());
547
+ const m = /^([^:]+):(.+?)(?::\d{0,4}-\d{0,4}|:\d{3,4})?$/u.exec(s.normalize("NFC").trim());
516
548
  if (!m || !JURISDICTIONS.includes(m[1].trim()))
517
549
  throw new UsageError(`--drop-jurisdiction "${s}": "<kind>:<name>", kind one of ${JURISDICTIONS.join(", ")}`, { code: "place.jurisdiction" });
518
550
  return { kind: m[1].trim(), name: m[2].trim() };
@@ -609,10 +641,68 @@ function originUnknown(p) {
609
641
  export function personsHand(p) {
610
642
  return !!p.coords && (!p.locating || p.locating.by === "person");
611
643
  }
612
- /** The forms queued for a place, and the place's forms, out of the queue. */
613
- function unqueue(file, keys) {
644
+ /**
645
+ * A step done on a place takes out of the queue every entry that leads to it now — the forms of its facts, and any
646
+ * form the queue keeps whose writing is a name of the record as the step leaves it (the facts written otherwise since,
647
+ * a rewrite, a merge) — and only those. `keys`: forms the step names itself. `only`: which entries go (all by default).
648
+ */
649
+ function unqueue(tree, file, changed, keys, only = () => true) {
650
+ const ids = new Set(changed.filter((p) => !p.retracted).map((p) => p.id));
651
+ const byId = new Set(changed.map((p) => p.id));
652
+ const recordOf = recordIndexOf([...tree.list("place").filter((p) => !byId.has(p.id)), ...changed]);
653
+ for (const [k, e] of Object.entries(file.queue)) {
654
+ const p = recordOf(e.form);
655
+ if ((keys.includes(k) && only(e, changed[0])) || (p && ids.has(p.id) && only(e, p)))
656
+ delete file.queue[k];
657
+ }
658
+ }
659
+ /**
660
+ * A queue entry whose place was dealt with after it was queued (identified, checked, asked, not located) — what an
661
+ * earlier strom left in the queue when the facts' form of it was written otherwise before the step. An entry the
662
+ * person put back (`back`) is never taken out so.
663
+ */
664
+ export function settledBy(p, e) {
665
+ if (e.back)
666
+ return false;
667
+ const l = p.locating;
668
+ const asked = !!l?.question && l.question.at >= e.at;
669
+ // checked after it was queued: the time of the check (an earlier strom kept only the day of a check found right — a
670
+ // later day; the same day, or a check undecided of no time, is not known to come after)
671
+ if (e.check)
672
+ return !!p.coords && (asked || (l?.checked ? l.checked >= e.at : !!l?.verified && l.verified > e.at.slice(0, 10)));
673
+ // a place on the map is never queued to be looked for (queuePlaces): on the map since, it was identified
674
+ return !!p.coords || asked || (!!p.unlocated && (l?.at ?? "") >= e.at);
675
+ }
676
+ /**
677
+ * The entries of the queue whose place was dealt with already (settledBy) taken out — nothing else: an entry of no
678
+ * record, of a record two places bear, or of a place still to be done stays. One operation; while the person has no
679
+ * step to undo, `strom place undo` puts them back (and they stay then). Inside the tree's lock; the forms taken out.
680
+ */
681
+ export function settleQueue(tree) {
682
+ const file = readPlacesFile(tree);
683
+ if (!Object.keys(file.queue).length)
684
+ return [];
685
+ const recordOf = recordIndex(tree);
686
+ const gone = {};
687
+ for (const [k, e] of Object.entries(file.queue)) {
688
+ const p = recordOf(e.form);
689
+ if (p && settledBy(p, e))
690
+ gone[k] = e;
691
+ }
692
+ const keys = Object.keys(gone);
693
+ if (!keys.length)
694
+ return [];
614
695
  for (const k of keys)
615
696
  delete file.queue[k];
697
+ const undo = keepUndo(file, { id: undoId(file), at: now(), places: {}, queue: gone });
698
+ const forms = keys.map((k) => gone[k].form);
699
+ write(tree, [], file, {
700
+ op: "place.settle",
701
+ targets: [],
702
+ reason: "queue entries of places dealt with already, left by an earlier version; nothing else taken out",
703
+ summary: `out of the queue, dealt with already: ${forms.length === 1 ? `place "${forms[0]}"` : `${forms.length} places (${forms.join(", ").slice(0, 600)})`} — undo: strom place undo ${undo}`,
704
+ });
705
+ return forms;
616
706
  }
617
707
  /**
618
708
  * A place identified surely (K2.2, K2.4): its coordinates, names, parent and jurisdictions written into its record (made
@@ -663,7 +753,7 @@ export function identifyPlace(tree, ref, input) {
663
753
  p.locating = locating;
664
754
  p.updated = now();
665
755
  const file = readPlacesFile(tree);
666
- unqueue(file, [...t.keys, ...more]);
756
+ unqueue(tree, file, [p], [...t.keys, ...more]);
667
757
  write(tree, [p], file, {
668
758
  op: "place.identify",
669
759
  targets: [p.id],
@@ -700,7 +790,7 @@ export function unlocatePlace(tree, ref, why, needs, by = "research") {
700
790
  delete p.locating.needs;
701
791
  p.updated = now();
702
792
  const file = readPlacesFile(tree);
703
- unqueue(file, t.keys);
793
+ unqueue(tree, file, [p], t.keys);
704
794
  write(tree, [p], file, { op: "place.unlocated", targets: [p.id], summary: `${p.id} ${p.names[0].name} not located: ${w.slice(0, 80)}` });
705
795
  return p;
706
796
  });
@@ -757,7 +847,7 @@ export function proposePlace(tree, ref, q) {
757
847
  p.locating = { ...(p.locating ?? originUnknown(p)), question };
758
848
  p.updated = now();
759
849
  const file = readPlacesFile(tree);
760
- unqueue(file, t.keys);
850
+ unqueue(tree, file, [p], t.keys);
761
851
  write(tree, [p], file, { op: "place.propose", targets: [p.id], summary: `${p.id} ${p.names[0].name}: a question for the person (${q.kind}${q.candidates ? `, ${q.candidates.length} candidates` : ""})` });
762
852
  return p;
763
853
  });
@@ -779,18 +869,21 @@ export function verifyPlace(tree, ref, result, details = {}) {
779
869
  throw new UsageError('why the jurisdiction is wrong: --why "…" (what the records or the reference say)', { code: "place.why" });
780
870
  const { more, dropped, today } = applyDetails(tree, p, t, details, forms);
781
871
  const base = p.locating ?? { by: "person", at: p.updated };
872
+ const { verifiedSource: _s, ...kept } = base;
873
+ const source = result.source ? { verifiedSource: { title: text(result.source.title, 300, "--source-title"), ...(result.source.url ? { url: result.source.url.trim() } : {}) } } : {};
782
874
  if (result.ok) {
783
- const { checkWhy: _c, verifiedWhy: _w, ...rest } = base;
784
- p.locating = { ...rest, verified: now().slice(0, 10), ...(why ? { verifiedWhy: why } : {}) };
875
+ const { checkWhy: _c, verifiedWhy: _w, ...rest } = kept;
876
+ const at = now();
877
+ p.locating = { ...rest, verified: at.slice(0, 10), checked: at, ...(why ? { verifiedWhy: why } : {}), ...source };
785
878
  }
786
879
  else {
787
880
  if (!why)
788
881
  throw new UsageError('why it cannot be decided: --undecided "…"', { code: "place.why" });
789
- p.locating = { ...base, checkWhy: why };
882
+ p.locating = { ...kept, checkWhy: why, checked: now(), ...source };
790
883
  }
791
884
  p.updated = now();
792
885
  const file = readPlacesFile(tree);
793
- unqueue(file, [...t.keys, ...more]);
886
+ unqueue(tree, file, [p], [...t.keys, ...more]);
794
887
  write(tree, [p], file, {
795
888
  op: "place.verify",
796
889
  targets: [p.id],
@@ -860,7 +953,11 @@ export function mergePlaces(tree, dupRef, intoRef, reason) {
860
953
  .filter((x) => !x.retracted && x.parent === dup.id && x.id !== into.id)
861
954
  .map((x) => ({ ...structuredClone(x), parent: into.id, updated: t }));
862
955
  const gone = { ...structuredClone(dup), retracted: { at: t, reason: `the same place as ${into.id}: ${why}` }, mergedInto: into.id, updated: t };
863
- write(tree, [into, gone, ...parts], undefined, {
956
+ // the queue's forms of the duplicate lead to the one record now: out where it was dealt with already
957
+ const file = readPlacesFile(tree);
958
+ const was = JSON.stringify(file.queue);
959
+ unqueue(tree, file, [into, gone, ...parts], [], (e, p) => settledBy(p, e));
960
+ write(tree, [into, gone, ...parts], JSON.stringify(file.queue) !== was ? file : undefined, {
864
961
  op: "place.merge",
865
962
  targets: [into.id, dup.id, ...parts.map((x) => x.id)].sort(),
866
963
  summary: `${dup.id} ${dup.names[0].name} merged into ${into.id} ${into.names[0].name}${parts.length ? ` (its parts ${parts.map((x) => x.id).join(", ")} with it)` : ""}`,
@@ -953,7 +1050,11 @@ export function rewriteForm(tree, form, to, opts) {
953
1050
  throw new UsageError('why the facts write it so: --reason "…"', { code: "place.why" });
954
1051
  const { records, done, left: gone } = applyChanges(tree, changes, "before");
955
1052
  place.updated = now();
956
- write(tree, [place, ...records], undefined, {
1053
+ // the form's entry of the queue leads to the record now: out where the place was dealt with already
1054
+ const file = readPlacesFile(tree);
1055
+ const was = JSON.stringify(file.queue);
1056
+ unqueue(tree, file, [place], [], (e, x) => settledBy(x, e));
1057
+ write(tree, [place, ...records], JSON.stringify(file.queue) !== was ? file : undefined, {
957
1058
  op: "place.rewrite",
958
1059
  targets: [place.id, ...records.map((r) => r.id)].sort(),
959
1060
  summary: `the facts' place "${form}" → ${name}${to.house ? ` (house ${to.house})` : ""}: ${done.length} facts in ${records.length} records${left.length ? `, ${left.length} left (another house)` : ""}`,
@@ -1057,12 +1158,12 @@ export function answerPlaces(tree, ids, take, opts = {}) {
1057
1158
  const file = readPlacesFile(tree);
1058
1159
  const before = new Map(places.map((p) => [p.id, tree.get(p.id)]));
1059
1160
  const undo = {
1060
- id: `u${Date.now().toString(36)}`,
1161
+ id: undoId(file),
1061
1162
  at: now(),
1062
1163
  places: Object.fromEntries(places.map((p) => [p.id, { before: before.get(p.id), after: p.updated }])),
1063
1164
  ...(done.length ? { facts: done } : {}),
1064
1165
  };
1065
- file.undo = undo;
1166
+ keepUndo(file, undo);
1066
1167
  write(tree, [...places, ...records], file, {
1067
1168
  op: "place.answer",
1068
1169
  targets: [...places.map((p) => p.id), ...records.map((r) => r.id)].sort(),
@@ -1073,15 +1174,16 @@ export function answerPlaces(tree, ids, take, opts = {}) {
1073
1174
  });
1074
1175
  }
1075
1176
  /**
1076
- * The person's last step on places undone (answers, a whole batch of them): each place as it was where nothing changed
1077
- * it since, the facts' places back where still so. Inside the tree's lock.
1177
+ * A step on places undone by its ID (the person's answers, a whole batch of them; names taken off; the queue tidied —
1178
+ * each of the last UNDO_KEPT once): each place as it was where nothing changed it since, the facts' places back where
1179
+ * still so, the queue's entries back. Inside the tree's lock.
1078
1180
  */
1079
1181
  export function undoPlaces(tree, id) {
1080
1182
  return tree.withTreeLock(() => {
1081
1183
  const file = readPlacesFile(tree);
1082
- const u = file.undo;
1083
- if (!u || u.id !== id)
1084
- throw new UsageError("nothing to undo of that step (only the last step on places, once)", { code: "places.undo.later" });
1184
+ const u = file.undo?.id === id ? file.undo : file.undos?.find((x) => x.id === id);
1185
+ if (!u)
1186
+ throw new UsageError(`nothing to undo of that step (each step on places once, the last ${UNDO_KEPT})`, { code: "places.undo.later" });
1085
1187
  const restored = [];
1086
1188
  const left = [];
1087
1189
  const records = [];
@@ -1096,13 +1198,42 @@ export function undoPlaces(tree, id) {
1096
1198
  restored.push(pid);
1097
1199
  }
1098
1200
  if (left.length)
1099
- throw new UsageError(`changed since: ${left.join(", ")} — the step is not undone`, { code: "places.undo.later", params: { places: left.join(", ") } });
1201
+ throw new UsageError(`changed since: ${left.join(", ")} — the step is not undone (a later step on the same place is undone first)`, {
1202
+ code: "places.undo.later",
1203
+ params: { places: left.join(", ") },
1204
+ });
1205
+ // an earlier step still to be undone that left a place as this undo puts it back: it knows the place by its new time
1206
+ for (const r of records) {
1207
+ const was = u.places[r.id].before.updated;
1208
+ for (const x of [file.undo, ...(file.undos ?? [])])
1209
+ if (x && x !== u && x.places[r.id]?.after === was)
1210
+ x.places[r.id] = { ...x.places[r.id], after: r.updated };
1211
+ }
1100
1212
  const back = applyChanges(tree, u.facts ?? [], "after");
1101
- delete file.undo;
1213
+ // the queue's entries the step took out: back, and kept there (never taken out by the tidying again)
1214
+ let queued = 0;
1215
+ for (const [k, e] of Object.entries(u.queue ?? {})) {
1216
+ if (file.queue[k])
1217
+ continue;
1218
+ file.queue[k] = { ...e, back: true };
1219
+ queued++;
1220
+ }
1221
+ // out of the steps to be undone; the one before it the newest again
1222
+ if (file.undo === u) {
1223
+ const before = file.undos?.pop();
1224
+ if (before)
1225
+ file.undo = before;
1226
+ else
1227
+ delete file.undo;
1228
+ }
1229
+ else
1230
+ file.undos = file.undos.filter((x) => x !== u);
1231
+ if (!file.undos?.length)
1232
+ delete file.undos;
1102
1233
  write(tree, [...records, ...back.records], file, {
1103
1234
  op: "place.undo",
1104
1235
  targets: [...restored, ...back.records.map((r) => r.id)].sort(),
1105
- summary: `the last step on places undone: ${restored.length} places${back.done.length ? `, ${back.done.length} facts' places back` : ""}`,
1236
+ summary: `a step on places undone (${u.id}): ${restored.length} places${back.done.length ? `, ${back.done.length} facts' places back` : ""}${queued ? `, ${queued} back in the queue` : ""}`,
1106
1237
  });
1107
1238
  return { restored, left };
1108
1239
  });
@@ -1160,6 +1291,8 @@ export function raisePlacesTask(tree, archive) {
1160
1291
  if (tree.dryRun || archive)
1161
1292
  return undefined;
1162
1293
  return tree.withTreeLock(() => {
1294
+ // what an earlier version left in the queue of places dealt with already: out first, so the count is right
1295
+ settleQueue(tree);
1163
1296
  const file = readPlacesFile(tree);
1164
1297
  const forms = factForms(tree);
1165
1298
  const recordOf = recordIndex(tree);
@@ -1220,6 +1353,55 @@ export function raisePlacesTask(tree, archive) {
1220
1353
  return task.id;
1221
1354
  });
1222
1355
  }
1356
+ /**
1357
+ * Names of a place record found wrong (a slip of a record kept as a form, another place's name) taken off, with the
1358
+ * reason: never today's name (the first), never a name a fact of the tree still writes for this place (set the facts
1359
+ * right first: strom place rewrite). One operation; while the person has no step to undo, `strom place undo` puts them
1360
+ * back. Inside the tree's lock.
1361
+ */
1362
+ export function dropPlaceNames(tree, ref, names, reason) {
1363
+ return tree.withTreeLock(() => {
1364
+ const id = placeIdOf(ref);
1365
+ const was = id ? tree.get(id) : undefined;
1366
+ if (!was || was.type !== "place" || was.retracted)
1367
+ throw new UsageError(`no place ${ref}: a place ID (L0012)`, { code: "place.unknown", hint: "strom place list" });
1368
+ const why = text(reason, 1000, "--why");
1369
+ if (!why)
1370
+ throw new UsageError('why the name is wrong: --why "…"', { code: "place.why" });
1371
+ if (!names.length)
1372
+ throw new UsageError('which name: --drop "<name>"', { code: "place.name" });
1373
+ const p = structuredClone(was);
1374
+ const forms = factForms(tree);
1375
+ const recordOf = recordIndex(tree);
1376
+ const dropped = [];
1377
+ for (const raw of names) {
1378
+ const n = raw.normalize("NFC").trim();
1379
+ const key = foldText(n);
1380
+ // the very writing first, else any of the same letters (any accent)
1381
+ const exact = p.names.findIndex((x) => x.name === n);
1382
+ const hit = exact >= 0 ? exact : p.names.findIndex((x) => foldText(x.name) === key);
1383
+ if (hit < 0)
1384
+ throw new UsageError(`${p.id} has no name "${n}" (it has ${p.names.map((x) => x.name).join(", ")})`, { code: "place.name" });
1385
+ if (hit === 0)
1386
+ throw new UsageError(`"${n}" is the first name of ${p.id} — today's name is set with --name (strom place verify ${p.id} --ok --name "…")`, { code: "place.name" });
1387
+ const f = forms.get(foldText(p.names[hit].name));
1388
+ if (f && recordOf(f.form)?.id === p.id && !p.names.some((x, i) => i !== hit && foldText(x.name) === foldText(p.names[hit].name)))
1389
+ throw new UsageError(`${f.facts === 1 ? "a fact" : `${f.facts} facts`} of the tree still write "${f.form}" — set them right first: strom place rewrite "${f.form}" --to ${p.id} --reason "…"`, { code: "place.form" });
1390
+ dropped.push(p.names[hit]);
1391
+ p.names.splice(hit, 1);
1392
+ }
1393
+ p.updated = now();
1394
+ const file = readPlacesFile(tree);
1395
+ const undo = keepUndo(file, { id: undoId(file), at: now(), places: { [p.id]: { before: was, after: p.updated } } });
1396
+ write(tree, [p], file, {
1397
+ op: "place.names",
1398
+ targets: [p.id],
1399
+ reason: why,
1400
+ summary: `${p.id} ${p.names[0].name}: name${dropped.length === 1 ? "" : "s"} taken off as wrong: ${dropped.map((x) => `${x.name}${x.lang ? `@${x.lang}` : ""}`).join(", ")} — ${why.slice(0, 80)} — undo: strom place undo ${undo}`,
1401
+ });
1402
+ return { place: p, dropped, undo };
1403
+ });
1404
+ }
1223
1405
  /**
1224
1406
  * The other names history's map is told of a place (strom history map --also) written among its record's names too —
1225
1407
  * the record is where the app and the identification read them. Nothing where the facts' place has no one record.
@@ -1361,19 +1543,17 @@ export function repairPlaces(tree) {
1361
1543
  if (!records.length)
1362
1544
  return [];
1363
1545
  file.repaired = PLACES_REPAIR;
1364
- if (!file.undo) {
1365
- file.undo = {
1366
- id: `u${Date.now().toString(36)}`,
1367
- at: now(),
1368
- places: Object.fromEntries(records.map((p) => [p.id, { before: tree.get(p.id), after: p.updated }])),
1369
- };
1370
- }
1546
+ const undo = keepUndo(file, {
1547
+ id: undoId(file),
1548
+ at: now(),
1549
+ places: Object.fromEntries(records.map((p) => [p.id, { before: tree.get(p.id), after: p.updated }])),
1550
+ });
1371
1551
  write(tree, records, file, {
1372
1552
  op: "place.repair",
1373
1553
  targets: records.map((p) => p.id).sort(),
1374
1554
  // a name read from its syntax, or written twice, is no name lost: the data check takes the step as reasoned
1375
1555
  reason: "what an earlier version wrote into the place records set right, nothing lost (undo kept)",
1376
- summary: `place records set right (${records.length}): ${said.join(" · ").slice(0, 1500)}${file.undo?.places[records[0].id] ? ` — undo: strom place undo ${file.undo.id}` : ""}`,
1556
+ summary: `place records set right (${records.length}): ${said.join(" · ").slice(0, 1500)} — undo: strom place undo ${undo}`,
1377
1557
  });
1378
1558
  return said;
1379
1559
  }
@@ -1423,11 +1603,15 @@ export function keepPlacesTask(tree) {
1423
1603
  tree.withTreeLock(() => repairPlaces(tree));
1424
1604
  return raisePlacesTask(tree, false);
1425
1605
  }
1426
- /** The jurisdiction of "<kind>:<name>[:from-to]". */
1606
+ /** The jurisdiction of "<kind>:<name>[:from-to]" — one year (":1899"): known so in that year, from and to alike. */
1427
1607
  export function parseJurisdiction(s) {
1428
- const m = /^([^:]+):([^:]+?)(?::(\d{3,4})?-(\d{3,4})?)?$/u.exec(s.normalize("NFC").trim());
1608
+ const m = /^([^:]+):([^:]+?)(?::(?:(\d{3,4})?-(\d{3,4})?|(\d{3,4})))?$/u.exec(s.normalize("NFC").trim());
1429
1609
  const kind = m?.[1]?.trim();
1430
1610
  if (!m || !kind || !JURISDICTIONS.includes(kind))
1431
- throw new UsageError(`--jurisdiction "${s}": "<kind>:<name>[:from-to]", kind one of ${JURISDICTIONS.join(", ")}`, { code: "place.jurisdiction" });
1432
- return { kind: kind, name: m[2].trim(), recordsets: [], ...(m[3] ? { from: Number(m[3]) } : {}), ...(m[4] ? { to: Number(m[4]) } : {}) };
1611
+ throw new UsageError(`--jurisdiction "${s}": "<kind>:<name>[:from-to]" or one year "<kind>:<name>:<year>", kind one of ${JURISDICTIONS.join(", ")}`, { code: "place.jurisdiction" });
1612
+ const from = m[3] ?? m[5];
1613
+ const to = m[4] ?? m[5];
1614
+ if (from && to && Number(from) > Number(to))
1615
+ throw new UsageError(`--jurisdiction "${s}": the years ${from}-${to} go backwards`, { code: "place.jurisdiction" });
1616
+ return { kind: kind, name: m[2].trim(), recordsets: [], ...(from ? { from: Number(from) } : {}), ...(to ? { to: Number(to) } : {}) };
1433
1617
  }
@@ -60,6 +60,7 @@ function view(k, p) {
60
60
  ...(why ? { why } : {}),
61
61
  ...(loc?.source ? { source: loc.source } : {}),
62
62
  ...(loc?.verified ? { verified: loc.verified } : {}),
63
+ ...(loc?.verifiedSource ? { verifiedSource: loc.verifiedSource } : {}),
63
64
  ...(loc?.checkWhy ? { checkWhy: loc.checkWhy } : {}),
64
65
  ...(!p.coords && p.unlocated && loc?.needs ? { needs: loc.needs } : {}),
65
66
  ...(q && (state === "proposed" || state === "conflict") ? { waiting: p.id } : {}),
@@ -78,7 +78,9 @@ export const SCHEMAS = {
78
78
  source: { t: "object", of: { title: S({ req: true, max: 300 }), url: S({ max: 2000 }) } },
79
79
  verified: S(),
80
80
  verifiedWhy: S({ max: 1000 }),
81
+ verifiedSource: { t: "object", of: { title: S({ req: true, max: 300 }), url: S({ max: 2000 }) } },
81
82
  checkWhy: S({ max: 1000 }),
83
+ checked: S(),
82
84
  needs: S({ max: 300 }),
83
85
  context: { t: "number", int: true, min: 0 },
84
86
  kept: { t: "object", of: { lat: { t: "number", req: true, min: -90, max: 90 }, lon: { t: "number", req: true, min: -180, max: 180 } } },
package/dist/core/tree.js CHANGED
@@ -31,7 +31,7 @@ export const TREE_FILE = "strom.json";
31
31
  export const SEAL_FOREIGN = "seal.foreign";
32
32
  // between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
33
33
  // at a release the release's own (package.json)
34
- export const VERSION = "1.26.0-beta.1";
34
+ export const VERSION = "1.26.0-beta.2";
35
35
  const GITIGNORE = `# Strom Research — local state and large files are not versioned
36
36
  .strom/
37
37
  /media/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strom-research",
3
- "version": "1.26.0-beta.1",
3
+ "version": "1.26.0-beta.2",
4
4
  "description": "Genealogical research toolkit for AI coding agents",
5
5
  "type": "module",
6
6
  "stromLayout": 2,