strom-research 1.22.0-beta.5 → 1.22.0-beta.6
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 +3 -1
- package/CHANGELOG.de.md +4 -1
- package/CHANGELOG.md +4 -1
- package/assets/lang/cs.json +8 -0
- package/assets/lang/de.json +8 -0
- package/dist/brief/history.js +28 -13
- package/dist/cli/ui.js +1 -0
- package/dist/commands/browse.js +12 -7
- package/dist/commands/history.js +23 -3
- package/dist/core/pastevents.js +260 -9
- package/dist/core/phrases.js +7 -0
- package/dist/core/session.js +4 -3
- package/dist/core/tree.js +1 -1
- package/package.json +1 -1
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.22.0
|
|
9
9
|
|
|
10
|
-
*Připravuje se: bety do 1.22.0-beta.
|
|
10
|
+
*Připravuje se: bety do 1.22.0-beta.6. Oddíl bude úplný s vydáním.*
|
|
11
11
|
|
|
12
12
|
- **Jméno jednou v tom, co čeká.** Úkol k materiálu poslanému z aplikace Strom jmenoval ve widgetu a na stránce
|
|
13
13
|
telefonu každého člověka dvakrát (podruhé v hranatých závorkách) a jeho text bez jmen nechal prázdné závorky; menu,
|
|
@@ -32,6 +32,8 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
32
32
|
prohledávají průběžně mezi sezeními rodinného výzkumu: zhruba každé 5. sezení (výchozí), každé 10., nebo jen když
|
|
33
33
|
není co jiného (`strom config set experiments.historyPace`, úvodní nastavení, Nastavení); kdy, řekne `strom plan`.
|
|
34
34
|
Nejdřív regiony s nejvíc osobami, země před státem nad ní.
|
|
35
|
+
Když jsou události regionu sebrané, roky, kdy v něm lidé stromu žili víc než 15 let po sobě bez jediné události,
|
|
36
|
+
se prohledají ještě jednou; prohledané roky se v žádném výzkumu znovu neotevřou, dokud se o region nepožádá znovu.
|
|
35
37
|
|
|
36
38
|
## 1.21.0
|
|
37
39
|
|
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.22.0
|
|
9
9
|
|
|
10
|
-
*In Vorbereitung: die Betas bis 1.22.0-beta.
|
|
10
|
+
*In Vorbereitung: die Betas bis 1.22.0-beta.6. Der Abschnitt ist mit dem Release vollständig.*
|
|
11
11
|
|
|
12
12
|
- **Jeder Name einmal in dem, was wartet.** Eine Aufgabe zu Material, das aus der Strom-App gesendet wurde, nannte im
|
|
13
13
|
Widget und auf der Seite des Telefons jede Person zweimal (das zweite Mal in eckigen Klammern), und ihr Text ohne
|
|
@@ -37,6 +37,9 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
37
37
|
Sitzungen der Familienforschung durchsucht: etwa jede 5. Sitzung (Standard), jede 10. oder nur, wenn nichts anderes
|
|
38
38
|
ansteht (`strom config set experiments.historyPace`, die Ersteinrichtung, die Einstellungen); wann, sagt `strom plan`.
|
|
39
39
|
Zuerst die Regionen mit den meisten Personen, ein Land vor dem Staat darüber.
|
|
40
|
+
Sind die Ereignisse einer Region gesammelt, werden die Jahre, in denen die Personen des Stammbaums dort mehr als
|
|
41
|
+
15 Jahre in Folge ohne ein Ereignis lebten, noch einmal durchsucht; durchsuchte Jahre werden in keiner Forschung
|
|
42
|
+
erneut aufgegriffen, bis die Region wieder angefragt wird.
|
|
40
43
|
|
|
41
44
|
## 1.21.0
|
|
42
45
|
|
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.22.0
|
|
9
9
|
|
|
10
|
-
*In preparation: the betas up to 1.22.0-beta.
|
|
10
|
+
*In preparation: the betas up to 1.22.0-beta.6. The section is complete with the release.*
|
|
11
11
|
|
|
12
12
|
- **Each name once in what waits.** A task for material sent from the Strom app named each person twice in the
|
|
13
13
|
widget and on the phone's page (the second time in brackets), and its text without names kept empty brackets; the
|
|
@@ -36,6 +36,9 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
36
36
|
sessions: about every 5th (the default), every 10th, or only when nothing else is left
|
|
37
37
|
(`strom config set experiments.historyPace`, the setup, the settings); `strom plan` says when. The regions
|
|
38
38
|
of the most people come first, a land before the state above it.
|
|
39
|
+
Once a region's events are gathered, the years the tree's people lived there for more than 15 in a row with no
|
|
40
|
+
event are looked through once more; years looked through are not raised again, in any research, until the region
|
|
41
|
+
is asked for again.
|
|
39
42
|
|
|
40
43
|
## 1.21.0
|
|
41
44
|
|
package/assets/lang/cs.json
CHANGED
|
@@ -731,6 +731,13 @@
|
|
|
731
731
|
"history.region.why": "co předkové zažili, ukázané u jejich životů",
|
|
732
732
|
"history.region.done": "důležité události období nalezené, každá ze stránky, kterou strom sám přečetl (nanejvýš asi 60); katalog regionu označený jako hotový",
|
|
733
733
|
"history.region.finished": "události regionu {region} jsou hotové",
|
|
734
|
+
"history.gaps.what": "Události dějin v regionu {region} pro roky, kdy katalog žádné nemá: {years}",
|
|
735
|
+
"history.gaps.why": "osoby stromu tam v těch letech žily a u jejich životů se z tehdejších dějin nic neukazuje",
|
|
736
|
+
"history.gaps.done": "zhruba jedna událost na 10–15 let každé mezery, každá ze stránky, kterou strom sám přečetl – nebo nic, když žádná událost hodná zápisu doložená není",
|
|
737
|
+
"history.gaps.filled": "v událostech regionu {region} už žádná mezera není",
|
|
738
|
+
"history.gaps.looked.none": "roky {years} v událostech regionu {region} už prohledal jiný výzkum: doložená událost v nich žádná",
|
|
739
|
+
"history.gaps.looked.some": "roky {years} v událostech regionu {region} už prohledal jiný výzkum: zapsaných událostí v nich: {n}",
|
|
740
|
+
"history.gaps.gone": "roky {years} už pro tento strom nejsou mezerou v událostech regionu {region}: údaje jeho osob v tom regionu se změnily",
|
|
734
741
|
"review.reread.what": "Přečíst znovu modelem {model}, o co se opírají údaje osoby {name}: {list}{more}",
|
|
735
742
|
"review.reread.why": "tyto zápisy četl jen jiný model; {model} je ten, kterým uživatel čte teď ({research})",
|
|
736
743
|
"review.reread.done": "zápis každého údaje přečtený naslepo modelem {model} (strom read M… --blind): shoda → poznámka u údaje; rozdíl → rozpor s oběma čteními",
|
|
@@ -2692,6 +2699,7 @@
|
|
|
2692
2699
|
"ui.plan.history.asked": "; přednost: {region}, na přání",
|
|
2693
2700
|
"ui.plan.history.order": "Regiony na řadě, po jednom: {list}",
|
|
2694
2701
|
"ui.plan.history.weight": "{region} (osob: {n})",
|
|
2702
|
+
"ui.plan.history.gaps": "{region}, roky bez událostí {years} (osob: {n})",
|
|
2695
2703
|
"ui.plan.history.prefer": "; přednost: místo {place} z nedávného sezení",
|
|
2696
2704
|
"ui.link.experiment.all": "Zapíná se v nastavení tohoto počítače: platí pro všechny výzkumy na něm (každý jde vypnout zvlášť).",
|
|
2697
2705
|
"ui.link.experiment.on.done": "Hledání událostí dějin je pro tento výzkum zapnuté.",
|
package/assets/lang/de.json
CHANGED
|
@@ -731,6 +731,13 @@
|
|
|
731
731
|
"history.region.why": "was die Vorfahren erlebten, bei ihren Lebensläufen gezeigt",
|
|
732
732
|
"history.region.done": "die wichtigen Ereignisse des Zeitraums gefunden, jedes von einer Seite, die strom selbst gelesen hat (höchstens etwa 60); der Katalog der Region als fertig vermerkt",
|
|
733
733
|
"history.region.finished": "die Ereignisse von {region} sind fertig",
|
|
734
|
+
"history.gaps.what": "Ereignisse der Geschichte in {region} für die Jahre, in denen der Katalog keines hat: {years}",
|
|
735
|
+
"history.gaps.why": "Personen des Stammbaums lebten in diesen Jahren dort, und bei ihren Lebensläufen erscheint nichts von der damaligen Geschichte",
|
|
736
|
+
"history.gaps.done": "etwa ein Ereignis alle 10–15 Jahre jeder Lücke, jedes von einer Seite, die strom selbst gelesen hat – oder keines, wenn kein Ereignis, das einen Eintrag wert ist, belegt ist",
|
|
737
|
+
"history.gaps.filled": "in den Ereignissen von {region} ist keine Lücke mehr",
|
|
738
|
+
"history.gaps.looked.none": "die Jahre {years} in den Ereignissen von {region} hat schon eine andere Forschung durchsucht: kein Ereignis darin belegt",
|
|
739
|
+
"history.gaps.looked.some": "die Jahre {years} in den Ereignissen von {region} hat schon eine andere Forschung durchsucht: darin geschriebene Ereignisse: {n}",
|
|
740
|
+
"history.gaps.gone": "die Jahre {years} sind für diesen Stammbaum keine Lücke in den Ereignissen von {region} mehr: die Angaben seiner Personen dort haben sich geändert",
|
|
734
741
|
"review.reread.what": "Mit {model} neu lesen, worauf die Angaben von {name} beruhen: {list}{more}",
|
|
735
742
|
"review.reread.why": "diese Einträge hat nur ein anderes Modell gelesen; mit {model} liest der Nutzer jetzt ({research})",
|
|
736
743
|
"review.reread.done": "der Eintrag jeder Angabe blind mit {model} gelesen (strom read M… --blind): übereinstimmend → Notiz an der Angabe; abweichend → ein Widerspruch mit beiden Lesungen",
|
|
@@ -2692,6 +2699,7 @@
|
|
|
2692
2699
|
"ui.plan.history.asked": "; zuerst: {region}, auf Bitte",
|
|
2693
2700
|
"ui.plan.history.order": "Regionen der Reihe nach, eine nach der anderen: {list}",
|
|
2694
2701
|
"ui.plan.history.weight": "{region} (Personen: {n})",
|
|
2702
|
+
"ui.plan.history.gaps": "{region}, die Jahre ohne Ereignisse {years} (Personen: {n})",
|
|
2695
2703
|
"ui.plan.history.prefer": "; zuerst: der Ort {place} aus einer kürzlichen Sitzung",
|
|
2696
2704
|
"ui.link.experiment.all": "Es wird in den Einstellungen dieses Computers eingeschaltet: für alle Forschungen darauf (jede lässt sich einzeln ausschalten).",
|
|
2697
2705
|
"ui.link.experiment.on.done": "Die Suche nach den Ereignissen der Geschichte ist für diese Forschung eingeschaltet.",
|
package/dist/brief/history.js
CHANGED
|
@@ -6,7 +6,7 @@ import { langName } from "../core/lang.js";
|
|
|
6
6
|
import { foldText } from "../core/text.js";
|
|
7
7
|
import { match } from "../cli/registry.js";
|
|
8
8
|
import { synopsis } from "./sheet.js";
|
|
9
|
-
import { catalogIds, EVENT_KINDS, HISTORY_PLACES_ORIGIN, HOLD_DAYS, readCatalog, readHidden, readPlaces, coveredBy, regionName, regionOfTask, regionPeriod, REGION_LANGS, unmappedPlaces, } from "../core/pastevents.js";
|
|
9
|
+
import { catalogIds, EVENT_KINDS, forGaps, GAP_DENSITY, gapsOfTask, gapText, HISTORY_PLACES_ORIGIN, HOLD_DAYS, readCatalog, readHidden, readPlaces, coveredBy, regionName, regionOfTask, regionPeriod, REGION_LANGS, unmappedPlaces, } from "../core/pastevents.js";
|
|
10
10
|
import { factPlaces } from "../gedcom/export.js";
|
|
11
11
|
/** About so many events a region at most: rather fewer. */
|
|
12
12
|
export const REGION_DENSITY = 60;
|
|
@@ -44,7 +44,7 @@ const ONLY_FROM_PAGES = [
|
|
|
44
44
|
export function historySections(tree, task, shared, alone) {
|
|
45
45
|
const region = regionOfTask(task);
|
|
46
46
|
if (region)
|
|
47
|
-
return regionSections(tree, region, shared, alone);
|
|
47
|
+
return regionSections(tree, region, shared, alone, forGaps(task) ? gapsOfTask(task) : undefined);
|
|
48
48
|
if (task.origin === HISTORY_PLACES_ORIGIN)
|
|
49
49
|
return placesSections(tree, shared, alone);
|
|
50
50
|
return undefined;
|
|
@@ -63,7 +63,10 @@ function closing(alone, lang, lines) {
|
|
|
63
63
|
].join("\n"),
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
/** What events a region's catalog keeps: those that shaped the lives of ordinary people there. */
|
|
67
|
+
const SHAPED = "those that shaped the lives of ordinary people there: wars and their levies, epidemics, famines, censuses, changes of the state and its borders, waves of emigration, laws on serfdom, land, schools, religion, great disasters; no lists of rulers, no battles far away, no happening of one village";
|
|
68
|
+
/** A region's session — or, with its gaps, a session filling the gaps of its finished catalog (K2.6). */
|
|
69
|
+
function regionSections(tree, region, shared, alone, gaps) {
|
|
67
70
|
const lang = tree.lang;
|
|
68
71
|
const c = shared ? readCatalog(shared, region) : undefined;
|
|
69
72
|
const name = regionName(c, lang, region);
|
|
@@ -80,19 +83,26 @@ function regionSections(tree, region, shared, alone) {
|
|
|
80
83
|
if (hiddenOf.has(e.id))
|
|
81
84
|
byKind.set(e.kind, (byKind.get(e.kind) ?? 0) + hiddenOf.get(e.id));
|
|
82
85
|
const room = Math.max(0, REGION_DENSITY - live.length);
|
|
86
|
+
const span = (g) => g.to - g.from + 1;
|
|
87
|
+
const wanted = gaps?.reduce((n, g) => n + Math.max(1, Math.round(span(g) / GAP_DENSITY)), 0) ?? 0;
|
|
83
88
|
const out = [];
|
|
84
89
|
out.push({
|
|
85
90
|
name: "history",
|
|
86
91
|
required: true,
|
|
87
92
|
pointer: "strom history status",
|
|
88
93
|
text: [
|
|
89
|
-
`## The history of ${name} (${region}) — for the Strom app`,
|
|
94
|
+
gaps ? `## The gaps in the history of ${name} (${region}) — for the Strom app` : `## The history of ${name} (${region}) — for the Strom app`,
|
|
90
95
|
"What the ancestors lived through: the events of this region's history, shown in the Strom app with the lives of the people of its places. " + NOTHING_ELSE,
|
|
91
|
-
|
|
96
|
+
gaps
|
|
97
|
+
? `- the region's catalog is finished, yet these years of this tree's people there have no event: ${gaps.map((g) => `${gapText(g)} (${span(g)} years)`).join(", ")} — look for events of these years alone (strom history add refuses others in this session); what the catalog has (below) is never looked for again`
|
|
98
|
+
: `- period: ${period.from}–${period.to} — this tree's places only (the oldest year of their facts in the region, never before 1600, to this year); another tree sharing the region adds its own years when it takes it`,
|
|
92
99
|
period.places.length
|
|
93
100
|
? `- this tree's places in it: ${period.places.map((p) => `${p.place}${p.from !== undefined || p.to !== undefined ? ` (${p.from ?? "…"}–${p.to ?? "…"})` : ""}${p.years.length ? ` facts ${years(Math.min(...p.years), Math.max(...p.years))}` : ""}`).join(" · ")}`
|
|
94
101
|
: "",
|
|
95
|
-
|
|
102
|
+
gaps
|
|
103
|
+
? `- how many: about one event every 10–15 years of each gap (about ${wanted} in all) — ${SHAPED}; none documented worth writing: nothing — a year is never filled for its own sake`
|
|
104
|
+
: `- how many: at most about ${REGION_DENSITY} events for the region, rather fewer (${live.length} written${room ? `, room for about ${room} more` : ": enough — only what is missing badly"}) — ${SHAPED}`,
|
|
105
|
+
gaps ? "" : "- spread over the period: the people of every part of it lived through something — rather an event each 10–15 years of it than many of a few decades",
|
|
96
106
|
`- kinds: ${EVENT_KINDS.join(", ")}`,
|
|
97
107
|
`- each event: its years (the day only when sure: --date); its texts in ${REGION_LANGS.join(", ")}, each ≤ 300 characters — factual and impersonal: what happened, where, how many; never addressed to anyone, never what to do, never speaking as yourself; --why-*: why it mattered for the people of the place; --tip-*: where a research may look because of it (a kind of record, an archive's series), impersonal too`,
|
|
98
108
|
"- a hard event (persecution, deportation, expulsion, famine, a pogrom): --sensitive; sober and factual, no judgement, no words of horror, and never a guess about the people of this tree — whom it touched is the reader's to see",
|
|
@@ -129,20 +139,25 @@ function regionSections(tree, region, shared, alone) {
|
|
|
129
139
|
text: [
|
|
130
140
|
`## Found already for the same places and years: ${cover.join(", ")} (finished, ${theirs.length} events) — the Strom app shows them at these places`,
|
|
131
141
|
"- write only what they lack: an event of the whole state they do not have; one of them again is never written (not even with --same-as)",
|
|
132
|
-
`- they lack nothing worth writing: strom history done ${region} at once, no page read`,
|
|
142
|
+
gaps ? "- they lack nothing worth writing: strom session close --done at once, no page read" : `- they lack nothing worth writing: strom history done ${region} at once, no page read`,
|
|
133
143
|
...theirs.slice(0, EVENTS_SHOWN).map(({ e }) => ` ${e.id} ${years(e.from, e.to)} ${e.kind} "${short(e.text[lang] ?? e.text.en ?? Object.values(e.text)[0] ?? "")}"`),
|
|
134
144
|
theirs.length > EVENTS_SHOWN ? ` … ${theirs.length - EVENTS_SHOWN} more: strom history status --app --json` : "",
|
|
135
145
|
].filter(Boolean).join("\n"),
|
|
136
146
|
});
|
|
137
147
|
}
|
|
138
|
-
const commands = [cmd(["history", "page"]), cmd(["history", "add"]), cmd(["history", "retract"]), cmd(["history", "status"]), cmd(["history", "done"])].filter(Boolean);
|
|
148
|
+
const commands = [cmd(["history", "page"]), cmd(["history", "add"]), cmd(["history", "retract"]), cmd(["history", "status"]), ...(gaps ? [] : [cmd(["history", "done"])])].filter(Boolean);
|
|
139
149
|
if (commands.length)
|
|
140
150
|
out.push({ name: "commands", first: true, pointer: "strom help history add", text: ["## Commands (each option as it is)", ...commands].join("\n") });
|
|
141
|
-
out.push(closing(alone, lang,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
151
|
+
out.push(closing(alone, lang, gaps
|
|
152
|
+
? [
|
|
153
|
+
`- the gaps looked at — events found, or none documented: strom session close --done "how many events, of which years and kinds, from which sources — or what was looked at" --next "…" (these years are never looked for again)`,
|
|
154
|
+
`- time up before: strom session close --continue --summary "…" --next "the years not looked at yet" (the region stays this tree's for ${HOLD_DAYS} days)`,
|
|
155
|
+
]
|
|
156
|
+
: [
|
|
157
|
+
`- the events of the period found (or nothing more worth writing): strom history done ${region}, then strom session close --done "how many events, of which kinds, from which sources" --next "…"`,
|
|
158
|
+
`- time up before: strom session close --continue --summary "…" --next "the kinds or years not looked at yet" (the region stays this tree's for ${HOLD_DAYS} days)`,
|
|
159
|
+
`- nothing could be found or read at all: strom session close --continue --summary "what was tried" --next "…"`,
|
|
160
|
+
]));
|
|
146
161
|
return out;
|
|
147
162
|
}
|
|
148
163
|
function placesSections(tree, shared, alone) {
|
package/dist/cli/ui.js
CHANGED
|
@@ -2543,6 +2543,7 @@ export const UI = {
|
|
|
2543
2543
|
"ui.plan.history.asked": "; first: {region}, as asked",
|
|
2544
2544
|
"ui.plan.history.order": "Regions in turn, one at a time: {list}",
|
|
2545
2545
|
"ui.plan.history.weight": "{region} (people: {n})",
|
|
2546
|
+
"ui.plan.history.gaps": "{region}, the years without events {years} (people: {n})",
|
|
2546
2547
|
"ui.plan.history.prefer": "; first: the place {place} from a recent session",
|
|
2547
2548
|
"ui.link.experiment.all": "It is turned on in this computer's settings: for every research on it (each can be turned off on its own).",
|
|
2548
2549
|
"ui.link.experiment.on.done": "The search for the events of history is on for this research.",
|
package/dist/commands/browse.js
CHANGED
|
@@ -16,7 +16,7 @@ export { humanTask };
|
|
|
16
16
|
import { lacksImages, waitsForArchive } from "../core/queue.js";
|
|
17
17
|
import { clockUp } from "../core/net.js";
|
|
18
18
|
import { forStory } from "../core/stories.js";
|
|
19
|
-
import { forHistory, HISTORY_PLACES_ORIGIN, historyOn, historyTurn, readCatalog, regionName, regionOfTask } from "../core/pastevents.js";
|
|
19
|
+
import { forGaps, forHistory, gapsOfTask, gapText, HISTORY_PLACES_ORIGIN, historyOn, historyTurn, readCatalog, regionName, regionOfTask } from "../core/pastevents.js";
|
|
20
20
|
import { isArchive } from "../core/mode.js";
|
|
21
21
|
import { actsAsArchive, stateOf } from "../core/state.js";
|
|
22
22
|
import { rankedQueue } from "./tasks.js";
|
|
@@ -244,7 +244,9 @@ register({
|
|
|
244
244
|
if (history?.regions.length)
|
|
245
245
|
out.push(ui(lang, "ui.plan.history.order", {
|
|
246
246
|
list: [
|
|
247
|
-
...history.regions
|
|
247
|
+
...history.regions
|
|
248
|
+
.slice(0, HISTORY_REGIONS)
|
|
249
|
+
.map((r) => (r.gaps ? ui(lang, "ui.plan.history.gaps", { region: r.name, years: r.gaps.join(", "), n: r.persons }) : ui(lang, "ui.plan.history.weight", { region: r.name, n: r.persons }))),
|
|
248
250
|
...(history.regions.length > HISTORY_REGIONS ? [`… (+${history.regions.length - HISTORY_REGIONS})`] : []),
|
|
249
251
|
].join(" · "),
|
|
250
252
|
}));
|
|
@@ -321,11 +323,14 @@ function historyPlan(ctx, tree) {
|
|
|
321
323
|
const prefer = !asked && turn.prefer?.task === task.id ? turn.prefer.place : undefined;
|
|
322
324
|
const when = task.origin === HISTORY_PLACES_ORIGIN ? ui(lang, "ui.plan.history.places") : turn.now ? ui(lang, "ui.plan.history.now") : turn.in === undefined ? ui(lang, "ui.plan.history.idle") : ui(lang, "ui.plan.history.in", { n: Math.max(1, turn.in) });
|
|
323
325
|
const weight = new Map((turn.order ?? []).map((w) => [w.id, w]));
|
|
324
|
-
// the regions as the queue ranks their tasks: asked for, a recent session's place, then by weight
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
// the regions as the queue ranks their tasks: asked for, a recent session's place, then by weight (the gaps of a
|
|
327
|
+
// finished one with their years)
|
|
328
|
+
const regions = ranked.flatMap((r) => {
|
|
329
|
+
const id = regionOfTask(r.task);
|
|
330
|
+
if (!id)
|
|
331
|
+
return [];
|
|
332
|
+
return [{ id, name: name(id), persons: weight.get(id)?.persons ?? 0, ...(forGaps(r.task) ? { gaps: gapsOfTask(r.task).map(gapText) } : {}) }];
|
|
333
|
+
});
|
|
329
334
|
return {
|
|
330
335
|
task,
|
|
331
336
|
turn,
|
package/dist/commands/history.js
CHANGED
|
@@ -13,7 +13,7 @@ import { clipMark } from "../core/excerpt.js";
|
|
|
13
13
|
import { resolvePerson } from "../core/people.js";
|
|
14
14
|
import { checkQuotes, cleanText, findPassages, mainQuote, pageParts, PageError, quotesShape, readPage, PART_CHARS } from "../core/pagecheck.js";
|
|
15
15
|
import { currentSession } from "../core/session.js";
|
|
16
|
-
import { addLocalEvent, addRegionEvent, askFirst, checkKind, checkTexts, checkYears, ensureRegion, factPlaceKey, hideEvent, historyAnswer, historyStatus, forHistory, historyOrder, historyTurn, mapPlace, markDone, noteTried, readCatalog, redoRegion, refuseBeforePage, REGION_LANGS, regionIdOk, regionOfTask, requireHistoryOn, requireRegionId, retractEvent, EVENT_KINDS, } from "../core/pastevents.js";
|
|
16
|
+
import { addLocalEvent, addRegionEvent, askFirst, checkKind, checkTexts, checkYears, ensureRegion, factPlaceKey, hideEvent, historyAnswer, historyStatus, forGaps, forHistory, gapsOfTask, gapText, historyOrder, historyTurn, mapPlace, markDone, noteTried, readCatalog, redoRegion, refuseBeforePage, REGION_LANGS, regionIdOk, regionOfTask, requireHistoryOn, requireRegionId, retractEvent, EVENT_KINDS, } from "../core/pastevents.js";
|
|
17
17
|
function shared(ctx) {
|
|
18
18
|
const s = ctx.settings.shared()?.value;
|
|
19
19
|
if (!s)
|
|
@@ -124,12 +124,21 @@ register({
|
|
|
124
124
|
return "asked first";
|
|
125
125
|
if (turn?.prefer?.task === t.id)
|
|
126
126
|
return `first: ${turn.prefer.place}`;
|
|
127
|
-
|
|
127
|
+
const gaps = forGaps(t) ? `gaps ${gapsOfTask(t).map(gapText).join(", ")}` : "";
|
|
128
|
+
return [gaps, w ? `${w.persons} people, ${w.facts} facts` : ""].filter(Boolean).join(" · ");
|
|
128
129
|
};
|
|
129
130
|
const when = !turn ? undefined : turn.places ? `the next session, the places to map first (${turn.places})` : turn.pace === "idle" ? "when the family research has nothing to do" : turn.now ? "the next session" : `in ~${Math.max(1, turn.in ?? 0)} sessions`;
|
|
130
131
|
return {
|
|
131
132
|
text: lines(`history of the places: ${s.on ? "on" : "off (the user alone turns it on: strom config set experiments.history on)"} · search: ${state.search}${state.why ? ` (${state.why})` : ""}`, `places of the facts: ${s.places.total} · mapped ${s.places.mapped} · not located ${s.places.unlocated} · waiting ${s.places.pending.length}`, shown.length ? `waiting: ${shown.join(" · ")}${s.places.pending.length > shown.length ? ` … (+${s.places.pending.length - shown.length}, --limit)` : ""}` : undefined, s.regions.length
|
|
132
|
-
? table(s.regions.map((r) => [
|
|
133
|
+
? table(s.regions.map((r) => [
|
|
134
|
+
r.id,
|
|
135
|
+
r.name?.en ?? "(no catalog)",
|
|
136
|
+
`${r.events} events`,
|
|
137
|
+
r.retracted ? `${r.retracted} taken back` : "",
|
|
138
|
+
r.done ? `done ${r.done.slice(0, 10)}` : r.heldBy ? `held by ${r.heldBy}` : "open",
|
|
139
|
+
r.gaps?.length ? `gaps: ${r.gaps.map(gapText).join(", ")}` : "",
|
|
140
|
+
r.tried ? `${r.tried} pages not read` : "",
|
|
141
|
+
]))
|
|
133
142
|
: "no regions yet", `local events: ${s.local.events}${s.local.retracted ? ` (${s.local.retracted} taken back)` : ""} · hidden by the person: ${s.hidden.total}`, turn ? `pace: ${turn.pace} (strom config set experiments.historyPace sometimes|rare|idle) · ${turn.since} sessions since the last of history · strom run takes the next: ${when}${turn.asked ? ` · first: ${turn.asked.task} (asked for ${turn.asked.region})` : turn.prefer ? ` · first: ${turn.prefer.task} (a recent session worked at ${turn.prefer.place})` : ""}` : undefined, tasks.length
|
|
134
143
|
? lines("tasks of history in the order strom run takes them, one a turn (strom run --task <T…> starts one now):", table(tasks.map((t, i) => [` ${i + 1}.`, t.id, t.origin, t.state, why(t)])))
|
|
135
144
|
: s.on
|
|
@@ -265,6 +274,17 @@ register({
|
|
|
265
274
|
if (shape)
|
|
266
275
|
throw new UsageError(`--quote: ${shape}`, { code: "history.quote", params: { why: shape } });
|
|
267
276
|
const s = sameness(opts);
|
|
277
|
+
// a session filling a finished catalog's gaps: an event of those years alone, before any request
|
|
278
|
+
const own = currentSession(tree, ctx.env, ctx.refs)?.task;
|
|
279
|
+
const ownTask = own ? tree.get(own) : undefined;
|
|
280
|
+
if (ownTask && forGaps(ownTask) && regionOfTask(ownTask) === region) {
|
|
281
|
+
const gaps = gapsOfTask(ownTask);
|
|
282
|
+
if (gaps.length && !gaps.some((g) => from <= g.to && g.from <= to))
|
|
283
|
+
throw new UsageError(`this session fills the gaps of ${region} only (${gaps.map(gapText).join(", ")}): ${from === to ? from : `${from}–${to}`} is outside them`, {
|
|
284
|
+
code: "history.gaps.outside",
|
|
285
|
+
params: { region },
|
|
286
|
+
});
|
|
287
|
+
}
|
|
268
288
|
refuseBeforePage(sh, region, { kind, from, to }, s);
|
|
269
289
|
let checked;
|
|
270
290
|
try {
|
package/dist/core/pastevents.js
CHANGED
|
@@ -30,13 +30,23 @@ import { lineValue } from "../gedcom/lines.js";
|
|
|
30
30
|
export const HISTORY_PLACES_ORIGIN = "history:places";
|
|
31
31
|
/** The prefix of a region's task (`history:region:<id>`, one a region). */
|
|
32
32
|
export const HISTORY_REGION_ORIGIN = "history:region:";
|
|
33
|
+
/** The prefix of a task that fills the gaps of a region's finished catalog (`history:gaps:<id>`, one a region). */
|
|
34
|
+
export const HISTORY_GAPS_ORIGIN = "history:gaps:";
|
|
33
35
|
/** The level the tasks of history take (an existing one: a new value of the list would raise SCHEMA_VERSION). */
|
|
34
36
|
export const HISTORY_LEVEL = "enrich";
|
|
35
37
|
/** A region's task origin. */
|
|
36
38
|
export function regionOrigin(region) {
|
|
37
39
|
return `${HISTORY_REGION_ORIGIN}${region}`;
|
|
38
40
|
}
|
|
39
|
-
/**
|
|
41
|
+
/** A region's task to fill the gaps of its finished catalog. */
|
|
42
|
+
export function gapsOrigin(region) {
|
|
43
|
+
return `${HISTORY_GAPS_ORIGIN}${region}`;
|
|
44
|
+
}
|
|
45
|
+
/** Is it a task that fills the gaps of a region's finished catalog (history:gaps:<id>)? */
|
|
46
|
+
export function forGaps(t) {
|
|
47
|
+
return typeof t.origin === "string" && t.origin.startsWith(HISTORY_GAPS_ORIGIN);
|
|
48
|
+
}
|
|
49
|
+
/** Is it a task of the history (history:places, history:region:<id>, history:gaps:<id>)? */
|
|
40
50
|
export function forHistory(t) {
|
|
41
51
|
return typeof t.origin === "string" && t.origin.startsWith("history:");
|
|
42
52
|
}
|
|
@@ -454,6 +464,7 @@ export function redoRegion(shared, region) {
|
|
|
454
464
|
throw new UsageError(`there is no region ${region}`, { code: "history.region.missing", params: { region } });
|
|
455
465
|
delete c.done;
|
|
456
466
|
delete c.holder;
|
|
467
|
+
delete c.gaps;
|
|
457
468
|
c.redo = new Date().toISOString();
|
|
458
469
|
writeCatalog(shared, c);
|
|
459
470
|
return c;
|
|
@@ -900,6 +911,7 @@ export function historyStatus(tree, shared, settings) {
|
|
|
900
911
|
...(c?.done ? { done: c.done.at } : {}),
|
|
901
912
|
...(c && heldElsewhere(c, tree.config.id) ? { heldBy: c.holder.tree } : {}),
|
|
902
913
|
tried: c?.tried.length ?? 0,
|
|
914
|
+
...(c?.done ? { gaps: regionGaps(tree, shared, id) } : {}),
|
|
903
915
|
};
|
|
904
916
|
});
|
|
905
917
|
const kindOf = new Map();
|
|
@@ -946,8 +958,13 @@ export function mergeCatalog(shared, given) {
|
|
|
946
958
|
}
|
|
947
959
|
const ids = new Set(had.events.map((e) => e.id));
|
|
948
960
|
const more = given.events.filter((e) => typeof e?.id === "string" && !ids.has(e.id));
|
|
949
|
-
|
|
961
|
+
// the gaps another computer's tree looked at: not looked at here again
|
|
962
|
+
const seen = new Set((had.gaps ?? []).map((g) => `${g.from}-${g.to}-${g.tree}`));
|
|
963
|
+
const gaps = (Array.isArray(given.gaps) ? given.gaps : []).filter((g) => Number.isInteger(g?.from) && Number.isInteger(g?.to) && !seen.has(`${g.from}-${g.to}-${g.tree}`));
|
|
964
|
+
if (!more.length && !gaps.length)
|
|
950
965
|
return { added: 0 };
|
|
966
|
+
if (gaps.length)
|
|
967
|
+
had.gaps = [...(had.gaps ?? []), ...gaps];
|
|
951
968
|
had.events.push(...more);
|
|
952
969
|
had.events.sort((a, b) => a.id.localeCompare(b.id));
|
|
953
970
|
const top = Math.max(0, ...had.events.map((e) => Number(/-(\d+)$/.exec(e.id)?.[1] ?? 0)));
|
|
@@ -970,9 +987,14 @@ export function unmappedPlaces(tree) {
|
|
|
970
987
|
export function regionName(c, lang, id = c?.region.id ?? "") {
|
|
971
988
|
return c?.region.name[lang] ?? c?.region.name.en ?? id;
|
|
972
989
|
}
|
|
973
|
-
/** The region of a task of history (history:region:<id>), else undefined. */
|
|
990
|
+
/** The region of a task of history (history:region:<id>, its gaps history:gaps:<id>), else undefined. */
|
|
974
991
|
export function regionOfTask(t) {
|
|
975
|
-
|
|
992
|
+
if (typeof t.origin !== "string")
|
|
993
|
+
return undefined;
|
|
994
|
+
for (const prefix of [HISTORY_REGION_ORIGIN, HISTORY_GAPS_ORIGIN])
|
|
995
|
+
if (t.origin.startsWith(prefix))
|
|
996
|
+
return t.origin.slice(prefix.length) || undefined;
|
|
997
|
+
return undefined;
|
|
976
998
|
}
|
|
977
999
|
/** History is looked for from this year at the earliest. */
|
|
978
1000
|
const EARLIEST_YEAR = 1600;
|
|
@@ -998,16 +1020,208 @@ export function regionPeriod(tree, region) {
|
|
|
998
1020
|
const from = Number.isFinite(oldest) ? Math.min(to, Math.max(EARLIEST_YEAR, oldest)) : EARLIEST_YEAR;
|
|
999
1021
|
return { from, to, places: places.sort((a, b) => a.place.localeCompare(b.place)) };
|
|
1000
1022
|
}
|
|
1023
|
+
// ── the gaps of a finished catalog (K2.6) ──────────────────────────────────
|
|
1024
|
+
/** A gap: more years than so many in a row the tree's people lived in a region with no event of its catalog. */
|
|
1025
|
+
export const GAP_YEARS = 15;
|
|
1026
|
+
/** A person lived in a region so many years before the first fact there and after the last. */
|
|
1027
|
+
export const LIVED_MARGIN = 5;
|
|
1028
|
+
/** An event of so many years or more (a state, a period of rule) marks only its beginning and its end, not the years between. */
|
|
1029
|
+
const LONG_EVENT = GAP_YEARS;
|
|
1030
|
+
/** About one event in so many years of a gap: what a session filling it looks for. */
|
|
1031
|
+
export const GAP_DENSITY = 12;
|
|
1032
|
+
/** A gap's years as a task and the brief write them: 1870–1913. */
|
|
1033
|
+
export function gapText(g) {
|
|
1034
|
+
return g.from === g.to ? String(g.from) : `${g.from}–${g.to}`;
|
|
1035
|
+
}
|
|
1036
|
+
/** The gaps a task of them was raised for (its `where`: 1870–1913 each). */
|
|
1037
|
+
export function gapsOfTask(t) {
|
|
1038
|
+
const out = [];
|
|
1039
|
+
for (const w of t.where ?? []) {
|
|
1040
|
+
const m = /^(\d{3,4})(?:\s*[–-]\s*(\d{3,4}))?$/u.exec(w.trim());
|
|
1041
|
+
if (m)
|
|
1042
|
+
out.push({ from: Number(m[1]), to: Number(m[2] ?? m[1]) });
|
|
1043
|
+
}
|
|
1044
|
+
return out;
|
|
1045
|
+
}
|
|
1046
|
+
/** Merged spans, both ends in: touching or overlapping ones joined. */
|
|
1047
|
+
function union(spans) {
|
|
1048
|
+
const out = [];
|
|
1049
|
+
for (const s of [...spans].sort((a, b) => a.from - b.from)) {
|
|
1050
|
+
const last = out.at(-1);
|
|
1051
|
+
if (last && s.from <= last.to + 1)
|
|
1052
|
+
last.to = Math.max(last.to, s.to);
|
|
1053
|
+
else
|
|
1054
|
+
out.push({ ...s });
|
|
1055
|
+
}
|
|
1056
|
+
return out;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* The years the tree's people lived in a region: each person from the first year of a fact at its places (in the years
|
|
1060
|
+
* each place belonged to it) to the last, LIVED_MARGIN before and after, within the years of those places' links and
|
|
1061
|
+
* never before 1600 or after this year — the spans of all of them joined. A family's fact counts for its partners;
|
|
1062
|
+
* a fact without a year counts for nothing.
|
|
1063
|
+
*/
|
|
1064
|
+
export function livedSpans(tree, region) {
|
|
1065
|
+
const r = reading(tree, undefined);
|
|
1066
|
+
const now = new Date().getFullYear();
|
|
1067
|
+
const of = new Map();
|
|
1068
|
+
const count = (who, e) => {
|
|
1069
|
+
if (!factWritten(e) || !e.place || !e.date)
|
|
1070
|
+
return;
|
|
1071
|
+
const key = lineValue(e.place);
|
|
1072
|
+
const m = key ? r.mapOf(key) : undefined;
|
|
1073
|
+
if (!m)
|
|
1074
|
+
return;
|
|
1075
|
+
const years = dateYears(e.date);
|
|
1076
|
+
for (const g of m.regions) {
|
|
1077
|
+
if (g.id !== region)
|
|
1078
|
+
continue;
|
|
1079
|
+
const inside = years.filter((y) => inRegion({ from: y, to: y }, g));
|
|
1080
|
+
if (!inside.length)
|
|
1081
|
+
continue;
|
|
1082
|
+
for (const p of who) {
|
|
1083
|
+
const w = of.get(p) ?? { years: [], lo: Infinity, hi: -Infinity };
|
|
1084
|
+
of.set(p, w);
|
|
1085
|
+
w.years.push(...inside);
|
|
1086
|
+
w.lo = Math.min(w.lo, g.from ?? -Infinity);
|
|
1087
|
+
w.hi = Math.max(w.hi, g.to ?? Infinity);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
const persons = tree.list("person").filter((p) => !p.retracted);
|
|
1092
|
+
const alive = new Set(persons.map((p) => p.id));
|
|
1093
|
+
for (const p of persons)
|
|
1094
|
+
for (const e of p.events)
|
|
1095
|
+
count([p.id], e);
|
|
1096
|
+
for (const f of tree.list("family")) {
|
|
1097
|
+
const who = f.partners.filter((p) => alive.has(p));
|
|
1098
|
+
if (who.length)
|
|
1099
|
+
for (const e of f.events)
|
|
1100
|
+
count(who, e);
|
|
1101
|
+
}
|
|
1102
|
+
const spans = [];
|
|
1103
|
+
for (const w of of.values()) {
|
|
1104
|
+
const from = Math.max(EARLIEST_YEAR, w.lo, Math.min(...w.years) - LIVED_MARGIN);
|
|
1105
|
+
const to = Math.min(now, w.hi, Math.max(...w.years) + LIVED_MARGIN);
|
|
1106
|
+
if (from <= to)
|
|
1107
|
+
spans.push({ from, to });
|
|
1108
|
+
}
|
|
1109
|
+
return union(spans);
|
|
1110
|
+
}
|
|
1001
1111
|
/**
|
|
1002
|
-
*
|
|
1112
|
+
* The gaps of a region's finished catalog for this tree (K2.6, Milan 28 Oct 2026 — a land's catalog with nothing
|
|
1113
|
+
* between 1869 and 1914 while the tree's people lived there): the runs of more than GAP_YEARS years the tree's people
|
|
1114
|
+
* lived there (livedSpans) with no event of the catalog — nor of the finished catalogs that hold its places and years
|
|
1115
|
+
* (coverOf: a state after its lands) — and not looked at before: the gaps a session of any tree looked at (the
|
|
1116
|
+
* catalog's `gaps`, noteGaps: a task of them done writes them there), this tree's tasks of them dropped (since the
|
|
1117
|
+
* person last asked for the region again). A task strom closed itself (raiseHistoryTasks: filled, looked at elsewhere)
|
|
1118
|
+
* is no look: events taken back later, its years are a gap again. An event of LONG_EVENT years or more marks its two
|
|
1119
|
+
* ends only. None for a catalog not finished (its own task looks for all), nor without people there. Nothing written.
|
|
1120
|
+
*/
|
|
1121
|
+
export function regionGaps(tree, shared, region) {
|
|
1122
|
+
if (!shared)
|
|
1123
|
+
return [];
|
|
1124
|
+
const r = reading(tree, shared);
|
|
1125
|
+
const c = r.catalog(region);
|
|
1126
|
+
if (!c?.done)
|
|
1127
|
+
return [];
|
|
1128
|
+
const lived = livedSpans(tree, region);
|
|
1129
|
+
if (!lived.length)
|
|
1130
|
+
return [];
|
|
1131
|
+
const covered = eventYears(r, region);
|
|
1132
|
+
const mark = (g) => {
|
|
1133
|
+
for (let y = g.from; y <= g.to; y++)
|
|
1134
|
+
covered.add(y);
|
|
1135
|
+
};
|
|
1136
|
+
for (const g of c.gaps ?? [])
|
|
1137
|
+
mark(g);
|
|
1138
|
+
for (const t of tree.list("task"))
|
|
1139
|
+
if (t.origin === gapsOrigin(region) && t.state === "dropped" && t.updated >= (c.redo ?? ""))
|
|
1140
|
+
for (const g of gapsOfTask(t))
|
|
1141
|
+
mark(g);
|
|
1142
|
+
return openRuns(lived, covered);
|
|
1143
|
+
}
|
|
1144
|
+
/** The years the events of a region's catalog (and of the finished catalogs that hold it) mark: a long one its ends. */
|
|
1145
|
+
function eventYears(r, region) {
|
|
1146
|
+
const covered = new Set();
|
|
1147
|
+
for (const id of [region, ...coverOf(r, region)])
|
|
1148
|
+
for (const e of r.catalog(id)?.events ?? []) {
|
|
1149
|
+
if (e.retracted)
|
|
1150
|
+
continue;
|
|
1151
|
+
if (e.to - e.from + 1 >= LONG_EVENT) {
|
|
1152
|
+
covered.add(e.from);
|
|
1153
|
+
covered.add(e.to);
|
|
1154
|
+
}
|
|
1155
|
+
else
|
|
1156
|
+
for (let y = e.from; y <= e.to; y++)
|
|
1157
|
+
covered.add(y);
|
|
1158
|
+
}
|
|
1159
|
+
return covered;
|
|
1160
|
+
}
|
|
1161
|
+
/** The runs of more than GAP_YEARS years of these spans no year of `covered` falls in. */
|
|
1162
|
+
function openRuns(spans, covered) {
|
|
1163
|
+
const out = [];
|
|
1164
|
+
for (const span of spans) {
|
|
1165
|
+
let start;
|
|
1166
|
+
for (let y = span.from; y <= span.to + 1; y++) {
|
|
1167
|
+
const open = y <= span.to && !covered.has(y);
|
|
1168
|
+
if (open && start === undefined)
|
|
1169
|
+
start = y;
|
|
1170
|
+
if (!open && start !== undefined) {
|
|
1171
|
+
if (y - start > GAP_YEARS)
|
|
1172
|
+
out.push({ from: start, to: y - 1 });
|
|
1173
|
+
start = undefined;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return out;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Why a gaps' task has nothing left to do (regionGaps none of its years): its years filled by the events of the
|
|
1181
|
+
* catalog ("filled"); else looked at by another tree's session (the catalog's `gaps`: "looked", with the events it
|
|
1182
|
+
* wrote in them); else no gap for this tree any more — its people's facts there changed ("gone").
|
|
1183
|
+
*/
|
|
1184
|
+
export function gapsClosed(tree, shared, region, t) {
|
|
1185
|
+
const r = reading(tree, shared);
|
|
1186
|
+
const mine = gapsOfTask(t);
|
|
1187
|
+
if (!openRuns(mine, eventYears(r, region)).length)
|
|
1188
|
+
return { why: "filled", added: 0 };
|
|
1189
|
+
const looked = (r.catalog(region)?.gaps ?? []).filter((g) => g.tree !== tree.config.id && mine.some((m) => g.from <= m.to && m.from <= g.to));
|
|
1190
|
+
if (looked.length)
|
|
1191
|
+
return { why: "looked", added: looked.reduce((n, g) => n + (g.added || 0), 0) };
|
|
1192
|
+
return { why: "gone", added: 0 };
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* A gaps' task finished (strom session close --done): its gaps written into the shared catalog as looked at — with the
|
|
1196
|
+
* events this tree wrote in them since the task was made, none too — and the region let go when this tree held it.
|
|
1197
|
+
*/
|
|
1198
|
+
export function noteGaps(shared, region, t, treeId) {
|
|
1199
|
+
const gaps = gapsOfTask(t);
|
|
1200
|
+
if (!regionIdOk(region) || !gaps.length)
|
|
1201
|
+
return;
|
|
1202
|
+
withCatalogs(shared, () => {
|
|
1203
|
+
const c = readCatalog(shared, region);
|
|
1204
|
+
if (!c)
|
|
1205
|
+
return;
|
|
1206
|
+
const at = new Date().toISOString();
|
|
1207
|
+
const mine = c.events.filter((e) => !e.retracted && e.by === treeId && e.at >= t.created);
|
|
1208
|
+
c.gaps = [...(c.gaps ?? []), ...gaps.map((g) => ({ ...g, at, tree: treeId, added: mine.filter((e) => overlap(e, g)).length }))];
|
|
1209
|
+
if (c.holder?.tree === treeId)
|
|
1210
|
+
delete c.holder;
|
|
1211
|
+
writeCatalog(shared, c);
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* A region taken by this tree for a session on its task (or on its gaps): the holder written into the shared catalog (refreshed when it
|
|
1003
1216
|
* is this tree's already). Another tree's hold within HOLD_DAYS stays: its ID is returned (nothing written).
|
|
1004
1217
|
*/
|
|
1005
|
-
export function takeRegion(shared, region, treeId) {
|
|
1218
|
+
export function takeRegion(shared, region, treeId, gaps = false) {
|
|
1006
1219
|
if (!regionIdOk(region))
|
|
1007
1220
|
return undefined;
|
|
1008
1221
|
return withCatalogs(shared, () => {
|
|
1009
1222
|
const c = readCatalog(shared, region);
|
|
1010
|
-
|
|
1223
|
+
// a finished catalog is held only while its gaps are filled
|
|
1224
|
+
if (!c || (c.done && !gaps))
|
|
1011
1225
|
return undefined;
|
|
1012
1226
|
if (heldElsewhere(c, treeId))
|
|
1013
1227
|
return c.holder.tree;
|
|
@@ -1275,7 +1489,8 @@ export function historyHeld(tree, shared, settings = new Settings(tree.env, {}))
|
|
|
1275
1489
|
/**
|
|
1276
1490
|
* The tasks of history raised while the experiment is on in a research (never in an archive, nor one waiting for its
|
|
1277
1491
|
* agent, never in a dry run): one to map the places of the facts while any is not mapped, one for each region of the
|
|
1278
|
-
* tree's places whose catalog is not finished
|
|
1492
|
+
* tree's places whose catalog is not finished, one for the gaps of a finished one (regionGaps) — level enrich,
|
|
1493
|
+
* priority 1, after all the research in the queue. Never a
|
|
1279
1494
|
* second one of the same origin open; a places' task again only for a place the last one did not name; a region's task
|
|
1280
1495
|
* dropped is the answer until the person asks for the region again (redo); one whose catalog was finished meanwhile
|
|
1281
1496
|
* (another tree, strom history done) is done. Inside the tree's lock, committed by the caller. Returns what it made.
|
|
@@ -1288,6 +1503,36 @@ export function raiseHistoryTasks(tree, shared, settings = new Settings(tree.env
|
|
|
1288
1503
|
const tasks = tree.list("task").filter(forHistory);
|
|
1289
1504
|
const made = [];
|
|
1290
1505
|
const add = (fields) => made.push(create(tree, "task", { level: HISTORY_LEVEL, priority: 1, subject: [], state: "open", ...fields }, (id) => `+${id} task "${fields.what.slice(0, 60)}" (history)`).id);
|
|
1506
|
+
// a finished catalog with gaps in the years of the tree's people there: one task to fill them (K2.6), paced as a
|
|
1507
|
+
// region's; none left (filled meanwhile, by another tree too): its waiting task is done — one at work closes itself
|
|
1508
|
+
const raiseGaps = (id, c) => {
|
|
1509
|
+
const origin = gapsOrigin(id);
|
|
1510
|
+
const live = tasks.filter((t) => t.origin === origin && LIVE_STATES.has(t.state));
|
|
1511
|
+
const gaps = regionGaps(tree, shared, id);
|
|
1512
|
+
if (!gaps.length) {
|
|
1513
|
+
// its result says why, truly: filled, looked at by another research (found or not), or no gap here any more
|
|
1514
|
+
for (const t of live.filter((x) => x.state === "open" || x.state === "parked")) {
|
|
1515
|
+
const { why, added } = gapsClosed(tree, shared, id, t);
|
|
1516
|
+
const key = why === "looked" ? (added ? "history.gaps.looked.some" : "history.gaps.looked.none") : why === "gone" ? "history.gaps.gone" : "history.gaps.filled";
|
|
1517
|
+
const result = phrase(lang, key, { region: regionName(c, lang, id), years: t.where.join(", "), n: added });
|
|
1518
|
+
update(tree, t.id, "task", (x) => ({ ...x, state: "done", result }), {
|
|
1519
|
+
op: "task.done",
|
|
1520
|
+
summary: `${t.id} done: no gap left in the catalog of ${id} (${why})`,
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
if (live.length)
|
|
1526
|
+
return;
|
|
1527
|
+
const years = gaps.map(gapText);
|
|
1528
|
+
add({
|
|
1529
|
+
what: phrase(lang, "history.gaps.what", { region: regionName(c, lang, id), years: years.join(", ") }),
|
|
1530
|
+
where: years,
|
|
1531
|
+
why: phrase(lang, "history.gaps.why"),
|
|
1532
|
+
doneWhen: phrase(lang, "history.gaps.done"),
|
|
1533
|
+
origin,
|
|
1534
|
+
});
|
|
1535
|
+
};
|
|
1291
1536
|
// the places of the facts
|
|
1292
1537
|
const unmapped = unmappedPlaces(tree);
|
|
1293
1538
|
const ofPlaces = tasks.filter((t) => t.origin === HISTORY_PLACES_ORIGIN);
|
|
@@ -1317,6 +1562,7 @@ export function raiseHistoryTasks(tree, shared, settings = new Settings(tree.env
|
|
|
1317
1562
|
op: "task.done",
|
|
1318
1563
|
summary: `${t.id} done: the catalog of ${id} is finished`,
|
|
1319
1564
|
});
|
|
1565
|
+
raiseGaps(id, c);
|
|
1320
1566
|
continue;
|
|
1321
1567
|
}
|
|
1322
1568
|
if (mine.some((t) => LIVE_STATES.has(t.state)))
|
|
@@ -1336,11 +1582,16 @@ export function raiseHistoryTasks(tree, shared, settings = new Settings(tree.env
|
|
|
1336
1582
|
return made;
|
|
1337
1583
|
});
|
|
1338
1584
|
}
|
|
1339
|
-
/**
|
|
1585
|
+
/**
|
|
1586
|
+
* A region's task finished (strom session close --done): its catalog finished too, when it is not yet; a task of its
|
|
1587
|
+
* gaps: those gaps looked at (noteGaps).
|
|
1588
|
+
*/
|
|
1340
1589
|
export function finishRegionOf(tree, shared, t) {
|
|
1341
1590
|
const region = regionOfTask(t);
|
|
1342
1591
|
if (!region || !shared || tree.dryRun)
|
|
1343
1592
|
return;
|
|
1593
|
+
if (forGaps(t))
|
|
1594
|
+
return noteGaps(shared, region, t, tree.config.id);
|
|
1344
1595
|
const c = readCatalog(shared, region);
|
|
1345
1596
|
if (c && !c.done)
|
|
1346
1597
|
markDone(shared, region, tree.config.id);
|
package/dist/core/phrases.js
CHANGED
|
@@ -83,6 +83,13 @@ export const PHRASES = {
|
|
|
83
83
|
"history.region.why": "what the ancestors lived through, shown with their lives",
|
|
84
84
|
"history.region.done": "the important events of the period found, each from a page strom read itself (about 60 at most); the region's catalog marked finished",
|
|
85
85
|
"history.region.finished": "the events of {region} are finished",
|
|
86
|
+
"history.gaps.what": "Events of history in {region} for the years its catalog has none: {years}",
|
|
87
|
+
"history.gaps.why": "the people of the tree lived there in those years, and their lives show nothing of the history then",
|
|
88
|
+
"history.gaps.done": "about one event every 10–15 years of each gap, each from a page strom read itself — or nothing, when no event worth writing is documented",
|
|
89
|
+
"history.gaps.filled": "no gap left in the events of {region}",
|
|
90
|
+
"history.gaps.looked.none": "the years {years} of the events of {region} were looked through by another research: no event documented in them",
|
|
91
|
+
"history.gaps.looked.some": "the years {years} of the events of {region} were looked through by another research: events written in them: {n}",
|
|
92
|
+
"history.gaps.gone": "the years {years} are no gap in the events of {region} for this tree any more: the facts of its people there changed",
|
|
86
93
|
"review.reread.what": "Read again with {model} what the facts of {name} rest on: {list}{more}",
|
|
87
94
|
"review.reread.why": "only another model read these entries; {model} is the one the user reads with now ({research})",
|
|
88
95
|
"review.reread.done": "each fact's entry read blind with {model} (strom read M… --blind): agreed → noted on the fact; differs → a conflict with both readings",
|
package/dist/core/session.js
CHANGED
|
@@ -20,7 +20,7 @@ import { readCodexConfig } from "../agents/effort.js";
|
|
|
20
20
|
import { clearSessionWait, permissionWait, waitingForDecision } from "./archivewait.js";
|
|
21
21
|
import { forgetLeftAlone } from "./leftalone.js";
|
|
22
22
|
import { Settings } from "./config.js";
|
|
23
|
-
import { regionOfTask, takeRegion } from "./pastevents.js";
|
|
23
|
+
import { forGaps, regionOfTask, takeRegion } from "./pastevents.js";
|
|
24
24
|
function currentFile(tree, env = tree.env) {
|
|
25
25
|
const w = env.STROM_WORKER;
|
|
26
26
|
return path.join(tree.root, ".strom", isWorkerId(w) ? `session-${w}.json` : "session.json");
|
|
@@ -190,11 +190,12 @@ export function startSession(tree, opts) {
|
|
|
190
190
|
const doing = { ...rest, state: "doing", updated: now() };
|
|
191
191
|
tree.put(doing, { op: "task.start", targets: [doing.id, s.id], summary: `${doing.id} start` });
|
|
192
192
|
}
|
|
193
|
-
// a region's task of history: this tree holds the region while it builds its catalog
|
|
193
|
+
// a region's task of history: this tree holds the region while it builds its catalog, or fills its gaps (another
|
|
194
|
+
// tree's task waits)
|
|
194
195
|
const region = task ? regionOfTask(task) : undefined;
|
|
195
196
|
const shared = region && !tree.dryRun ? new Settings(tree.env, {}).shared()?.value : undefined;
|
|
196
197
|
if (region && shared)
|
|
197
|
-
takeRegion(shared, region, tree.config.id);
|
|
198
|
+
takeRegion(shared, region, tree.config.id, forGaps(task));
|
|
198
199
|
setCurrent(tree, s.id);
|
|
199
200
|
return s;
|
|
200
201
|
});
|
package/dist/core/tree.js
CHANGED
|
@@ -29,7 +29,7 @@ export const TREE_FILE = "strom.json";
|
|
|
29
29
|
export const SEAL_FOREIGN = "seal.foreign";
|
|
30
30
|
// between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
|
|
31
31
|
// at a release the release's own (package.json)
|
|
32
|
-
export const VERSION = "1.22.0-beta.
|
|
32
|
+
export const VERSION = "1.22.0-beta.6";
|
|
33
33
|
const GITIGNORE = `# Strom Research — local state and large files are not versioned
|
|
34
34
|
.strom/
|
|
35
35
|
/media/
|