strom-research 1.23.0-beta.2 → 1.23.0-beta.4
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 +16 -3
- package/CHANGELOG.de.md +17 -2
- package/CHANGELOG.md +17 -3
- package/assets/lang/cs.json +12 -0
- package/assets/lang/de.json +12 -0
- package/assets/method/narrate.md +5 -0
- package/dist/brief/brief.js +43 -0
- package/dist/cli/menu-links.js +3 -1
- package/dist/cli/ui.js +6 -0
- package/dist/commands/checks.js +9 -14
- package/dist/commands/guide.js +2 -1
- package/dist/core/integrity.js +21 -1
- package/dist/core/oldsettings.js +14 -2
- package/dist/core/phrases.js +6 -0
- package/dist/core/queue.js +3 -3
- package/dist/core/seal.js +26 -14
- package/dist/core/stories.js +77 -15
- 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.23.0
|
|
9
9
|
|
|
10
|
-
*Připravuje se: bety do 1.23.0-beta.
|
|
10
|
+
*Připravuje se: bety do 1.23.0-beta.4. Oddíl bude úplný s vydáním.*
|
|
11
11
|
|
|
12
12
|
- **Osoba ukázaná zvoleným jménem.** Žena narozená pod jedním jménem, provdaná pod druhým a po rozvodu zase u prvního
|
|
13
13
|
se dá ukazovat tímto jménem znovu, aniž by se přidávalo podruhé: `strom name primary <osoba> "<jméno>"` (a
|
|
@@ -20,8 +20,21 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
20
20
|
„nikdy `strom repair`“ a `strom repair` vrátil zpět práci zapsanou klíčem terminálu. `strom repair` teď vrací jen
|
|
21
21
|
to, co se udělalo mimo strom, práce obou klíčů zůstává; potom klíče sjednotí `strom seal adopt` ve vlastním
|
|
22
22
|
terminálu – `strom check` říká oba kroky v tomto pořadí. Spuštěný z aplikace agenta balené pro Windows nechá `strom
|
|
23
|
-
repair` takový výzkum beze změny a odkáže na terminál
|
|
24
|
-
řekne, že výzkum znovu zapečetil,
|
|
23
|
+
repair` takový výzkum beze změny a odkáže na terminál; odmítnutý zápis tam odkáže na terminál také, už ne nejdřív
|
|
24
|
+
na `strom repair`. Po commitu gitem, který nezměnil žádný záznam, `strom repair` řekne, že výzkum znovu zapečetil,
|
|
25
|
+
už ne „nothing to repair“.
|
|
26
|
+
- **Pečetní klíč i tam, kde ho hledá starší strom (Windows).** Klíč převzatý z kopie aplikace balené pro Windows se
|
|
27
|
+
teď ukládá i do `%APPDATA%\strom\keys`, kde ho hledá strom 1.22.1 nebo starší v terminálu: po návratu ke starší verzi
|
|
28
|
+
zůstává výzkum na tomto počítači zapečetěný. Klíč, který tam už je, se nikdy nepřepíše.
|
|
29
|
+
- **Vyprávění páru schválené z aplikace Strom.** Dotaz v terminálu teď mluví o vyprávění páru, už ne o vyprávění
|
|
30
|
+
osoby.
|
|
31
|
+
- **Vyprávění páru navrhuje výzkum sám.** Jakmile záznamy vyprávějí společný život páru (svatbu nebo dítě a další
|
|
32
|
+
dva údaje) a jeden z dvojice už má vlastní vyprávění, výzkum navrhne vyprávění páru stejně jako vyprávění osoby –
|
|
33
|
+
jen společný život (svatba, domácnost, bydliště, děti, ovdovění); vyprávění partnerů odkáže, neopakuje je.
|
|
34
|
+
Vyprávění osoby napsané potom shrne manželství jednou větou; žádné už napsané vyprávění se kvůli tomu nezkracuje.
|
|
35
|
+
`strom config set stories no` vypne i tato.
|
|
36
|
+
- **`strom repair` jedním jazykem.** Každý jeho řádek („obnoveno“, „není co opravit“ …) je pro člověka v jazyce
|
|
37
|
+
výzkumu; agent je čte anglicky jako dřív.
|
|
25
38
|
|
|
26
39
|
## 1.22.2
|
|
27
40
|
|
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.23.0
|
|
9
9
|
|
|
10
|
-
*In Vorbereitung: die Betas bis 1.23.0-beta.
|
|
10
|
+
*In Vorbereitung: die Betas bis 1.23.0-beta.4. Der Abschnitt ist mit dem Release vollständig.*
|
|
11
11
|
|
|
12
12
|
- **Eine Person unter dem gewählten Namen.** Eine Frau, unter einem Namen geboren, unter einem anderen verheiratet und
|
|
13
13
|
nach einer Scheidung wieder beim ersten, lässt sich wieder unter diesem Namen zeigen, ohne dass er ein zweites Mal
|
|
@@ -25,7 +25,22 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
25
25
|
check` nennt beide Schritte in dieser Reihenfolge. Aus der für Windows paketierten Desktop-App eines Agenten
|
|
26
26
|
gestartet, lässt `strom repair` eine solche Forschung unverändert und verweist auf das Terminal. Nach einem Commit
|
|
27
27
|
mit git, der keinen Eintrag änderte, sagt `strom repair`, dass es die Forschung neu versiegelt hat, nicht mehr
|
|
28
|
-
„nothing to repair“.
|
|
28
|
+
„nothing to repair“. Ein dort abgelehnter Schreibvorgang verweist ebenfalls auf das Terminal, nicht mehr zuerst auf
|
|
29
|
+
`strom repair`.
|
|
30
|
+
- **Der Siegelschlüssel auch dort, wo ein älteres strom sucht (Windows).** Ein aus der Kopie einer für Windows
|
|
31
|
+
paketierten App übernommener Schlüssel liegt jetzt auch in `%APPDATA%\strom\keys`, wo ihn strom 1.22.1 oder älter
|
|
32
|
+
im Terminal sucht: zurück bei einer älteren Version bleibt die Forschung auf diesem Computer versiegelt. Ein Schlüssel,
|
|
33
|
+
der dort schon liegt, wird nie ersetzt.
|
|
34
|
+
- **Die Erzählung eines Paares, aus der Strom-App freigegeben.** Die Frage im Terminal nennt jetzt die Erzählung des
|
|
35
|
+
Paares, nicht mehr die einer Person.
|
|
36
|
+
- **Die Erzählung eines Paares, von der Forschung vorgeschlagen.** Sobald Einträge das gemeinsame Leben eines Paares
|
|
37
|
+
erzählen (die Hochzeit oder ein Kind und zwei weitere Angaben) und einer der beiden schon eine eigene Erzählung hat,
|
|
38
|
+
schlägt die Forschung die Erzählung des Paares vor wie die einer Person – nur das gemeinsame Leben (Hochzeit,
|
|
39
|
+
Haushalt, Wohnorte, Kinder, Witwenschaft); auf die Erzählungen der Partner wird verwiesen, sie werden nicht
|
|
40
|
+
wiederholt. Eine danach geschriebene Erzählung einer Person fasst die Ehe in einem Satz zusammen; keine schon
|
|
41
|
+
geschriebene wird dafür gekürzt. `strom config set stories no` schaltet auch diese aus.
|
|
42
|
+
- **`strom repair` in einer Sprache.** Jede Zeile („wiederhergestellt“, „nichts zu reparieren“ …) steht für eine
|
|
43
|
+
Person in der Sprache der Forschung; ein Agent liest sie wie bisher auf Englisch.
|
|
29
44
|
|
|
30
45
|
## 1.22.2
|
|
31
46
|
|
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.23.0
|
|
9
9
|
|
|
10
|
-
*In preparation: the betas up to 1.23.0-beta.
|
|
10
|
+
*In preparation: the betas up to 1.23.0-beta.4. The section is complete with the release.*
|
|
11
11
|
|
|
12
12
|
- **A person shown by the name chosen.** A woman born under one name, married under another and back at the first
|
|
13
13
|
after a divorce can be shown by that name again without it being added twice: `strom name primary <person>
|
|
@@ -20,8 +20,22 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
20
20
|
and never `strom repair` at once, and `strom repair` undid the work written with the terminal's key. `strom repair`
|
|
21
21
|
now undoes only what was done outside strom, the work of both keys stays; then `strom seal adopt` in a terminal
|
|
22
22
|
unites the keys — `strom check` says the two steps in this order. Started by an agent's desktop app packaged for
|
|
23
|
-
Windows, `strom repair` leaves such a research as it is and points to the terminal
|
|
24
|
-
|
|
23
|
+
Windows, `strom repair` leaves such a research as it is and points to the terminal; a write refused there points to
|
|
24
|
+
the terminal too, no longer to `strom repair` first. After a commit made with git that changed no record, `strom
|
|
25
|
+
repair` says it sealed the research again, no longer "nothing to repair".
|
|
26
|
+
- **The seal key also where an older strom looks (Windows).** A key taken over from the copy of an app packaged for
|
|
27
|
+
Windows is now kept in `%APPDATA%\strom\keys` too, where a terminal's strom 1.22.1 or older looks for it: back on an
|
|
28
|
+
older version, the research is still sealed on this computer. A key there already is never replaced.
|
|
29
|
+
- **A couple's story approved from the Strom app.** The question in the terminal now names the story of the couple,
|
|
30
|
+
no longer of a person.
|
|
31
|
+
- **A couple's story proposed by the research.** Once records tell a couple's life together (their wedding or a
|
|
32
|
+
child, and two more facts) and one of the two has a story of their own, the research proposes the couple's story
|
|
33
|
+
as it does a person's — their life together alone (the wedding, the household, the homes, the children, the
|
|
34
|
+
widowhood), the partners' stories referred to, never told again. A person's story written after it says the
|
|
35
|
+
marriage in a sentence; no story written already is shortened for it. `strom config set stories no` turns these off
|
|
36
|
+
too.
|
|
37
|
+
- **`strom repair` in one language.** Every line it writes ("restored", "nothing to repair", …) is in the research's
|
|
38
|
+
language for a person; an agent reads them in English as before.
|
|
25
39
|
|
|
26
40
|
## 1.22.2
|
|
27
41
|
|
package/assets/lang/cs.json
CHANGED
|
@@ -697,6 +697,12 @@
|
|
|
697
697
|
"story.where": "zapsané údaje (strom person show {id})",
|
|
698
698
|
"story.why": "{count, plural, one {# údaj ze zápisů vypráví} few {# údaje ze zápisů vyprávějí} other {# údajů ze zápisů vypráví}} život (generace {generation} výzkumu {research}); rodina si ho přečte v rodinné knize aplikace Strom",
|
|
699
699
|
"story.done": "strom story set {id} se všemi údaji, o které se opírá (--fact) – koncept, dokud ho uživatel neschválí",
|
|
700
|
+
"story.couple.what": "Napsat vyprávění páru: {a} a {b}",
|
|
701
|
+
"story.couple.more.what": "Doplnit vyprávění páru: {a} a {b} – {count, plural, one {# nový údaj} few {# nové údaje} other {# nových údajů}}",
|
|
702
|
+
"story.couple.where": "zapsané údaje páru (strom family show {id})",
|
|
703
|
+
"story.couple.partner": "vyprávění: {name}, už napsané – odkázat, neopakovat (strom story show {id})",
|
|
704
|
+
"story.couple.why": "{count, plural, one {# údaj ze zápisů vypráví} few {# údaje ze zápisů vyprávějí} other {# údajů ze zápisů vypráví}} jejich společný život (generace {generation} výzkumu {research}); život každého z nich je v jeho vlastním vyprávění",
|
|
705
|
+
"story.couple.done": "strom story set {id} se všemi údaji, o které se opírá (--fact): jen společný život – svatba, domácnost, bydliště, děti, ovdovění – koncept, dokud ho uživatel neschválí",
|
|
700
706
|
"story.sources.what": "Mimo matriky, pro vyprávění: {name} – noviny, adresáře, vojenské, pozemkové a soudní záznamy, hroby, dějiny místa",
|
|
701
707
|
"story.sources.where": "digitalizované noviny, knihy a archivy země a doby; web (rejstříky a rodokmeny jsou jen vodítko)",
|
|
702
708
|
"story.sources.why": "vyprávění má říct víc než matriky – a každé slovo pořád na záznamu; potom strom navrhne vyprávění",
|
|
@@ -1956,6 +1962,7 @@
|
|
|
1956
1962
|
"ui.link.story.none": "Vyprávění o {person} zatím není. Nic se nestalo.",
|
|
1957
1963
|
"ui.link.story.final": "Vyprávění o {person} už je schválené.",
|
|
1958
1964
|
"ui.link.what.story": "schválit vyprávění osoby {person}, jak je (už nebude návrh).",
|
|
1965
|
+
"ui.link.what.story.couple": "schválit vyprávění páru {couple}, jak je (už nebude návrh).",
|
|
1959
1966
|
"ui.link.story.done": "✓ Vyprávění je schválené. Po novém otevření výzkumu v aplikaci Strom tam bude bez štítku Návrh.",
|
|
1960
1967
|
"ui.link.sync.none": "{input} není v tomto výzkumu poslání z aplikace Strom. Nic se nestalo.",
|
|
1961
1968
|
"ui.link.sync.was": "Toto poslání už bylo vráceno.",
|
|
@@ -3112,6 +3119,11 @@
|
|
|
3112
3119
|
"ui.check.otherkey.afterrepair.hint": "po strom repair sjednotit ve vlastním terminálu: strom seal adopt",
|
|
3113
3120
|
"ui.repair.resealed": "znovu zapečetěno – commit udělaný mimo strom nezměnil žádný záznam",
|
|
3114
3121
|
"ui.repair.unite": "dál: sjednotit klíče stromu na tomto počítači ve vlastním terminálu: strom seal adopt",
|
|
3122
|
+
"ui.repair.nothing": "není co opravit – každý záznam odpovídá své pečeti",
|
|
3123
|
+
"ui.repair.restored": "obnoveno {file}",
|
|
3124
|
+
"ui.repair.removed": "odebráno {file}",
|
|
3125
|
+
"ui.repair.from": "zapečetěný stav: {commit} · kopie nahrazených souborů: .strom/quarantine/",
|
|
3126
|
+
"ui.repair.still": "{n, plural, one {zbývá # problém} few {zbývají # problémy} other {zbývá # problémů}} → strom verify",
|
|
3115
3127
|
"ui.error.seal.repair-in-terminal": "výzkum „{name}“ obsahuje práci zapečetěnou klíčem stromu z terminálu, který strom spuštěný z aplikace nevidí: nic se neopravilo",
|
|
3116
3128
|
"ui.error.seal.repair-in-terminal.hint": "ve vlastním terminálu: strom repair, pak strom seal adopt – ten vidí oba klíče a vrátí jen to, co se udělalo mimo strom",
|
|
3117
3129
|
"ui.menu.seal": "Zapečetěno na jiném počítači: zapisovat tu jde až po převzetí (strom seal adopt).",
|
package/assets/lang/de.json
CHANGED
|
@@ -697,6 +697,12 @@
|
|
|
697
697
|
"story.where": "die erfassten Angaben (strom person show {id})",
|
|
698
698
|
"story.why": "{count, plural, one {# Angabe aus Einträgen erzählt} other {# Angaben aus Einträgen erzählen}} das Leben (Generation {generation} der Forschung {research}); die Familie liest es im Familienbuch der Strom-App",
|
|
699
699
|
"story.done": "strom story set {id} mit jeder Angabe, auf der sie beruht (--fact) – ein Entwurf, bis der Benutzer sie freigibt",
|
|
700
|
+
"story.couple.what": "Die Geschichte des Paares schreiben: {a} und {b}",
|
|
701
|
+
"story.couple.more.what": "Die Geschichte des Paares ergänzen: {a} und {b} – {count, plural, one {# neue Angabe} other {# neue Angaben}}",
|
|
702
|
+
"story.couple.where": "die erfassten Angaben des Paares (strom family show {id})",
|
|
703
|
+
"story.couple.partner": "die Geschichte: {name}, schon geschrieben – darauf verweisen, nicht wiederholen (strom story show {id})",
|
|
704
|
+
"story.couple.why": "{count, plural, one {# Angabe aus Einträgen erzählt} other {# Angaben aus Einträgen erzählen}} ihr gemeinsames Leben (Generation {generation} der Forschung {research}); das eigene Leben jedes der beiden steht in seiner eigenen Geschichte",
|
|
705
|
+
"story.couple.done": "strom story set {id} mit jeder Angabe, auf der sie beruht (--fact): nur das gemeinsame Leben – die Hochzeit, der Haushalt, die Wohnorte, die Kinder, die Witwenschaft – ein Entwurf, bis der Benutzer sie freigibt",
|
|
700
706
|
"story.sources.what": "Über die Kirchenbücher hinaus, für die Geschichte: {name} – Zeitungen, Adressbücher, Militär-, Grund- und Gerichtsakten, Gräber, die Geschichte des Ortes",
|
|
701
707
|
"story.sources.where": "die digitalisierten Zeitungen, Bücher und Archive des Landes und der Zeit; das Web (Register und Stammbäume nur als Hinweis)",
|
|
702
708
|
"story.sources.why": "die Geschichte soll mehr erzählen als die Kirchenbücher – jedes Wort weiterhin auf einer Quelle; danach schlägt strom die Geschichte vor",
|
|
@@ -1956,6 +1962,7 @@
|
|
|
1956
1962
|
"ui.link.story.none": "{person} hat noch keine Erzählung. Es ist nichts geschehen.",
|
|
1957
1963
|
"ui.link.story.final": "Die Erzählung von {person} ist bereits freigegeben.",
|
|
1958
1964
|
"ui.link.what.story": "die Erzählung von {person} so freigeben, wie sie ist (dann kein Entwurf mehr).",
|
|
1965
|
+
"ui.link.what.story.couple": "die Erzählung des Paares {couple} so freigeben, wie sie ist (dann kein Entwurf mehr).",
|
|
1959
1966
|
"ui.link.story.done": "✓ Die Erzählung ist freigegeben. Wird die Forschung in der Strom-App erneut geöffnet, steht sie ohne das Etikett Entwurf da.",
|
|
1960
1967
|
"ui.link.sync.none": "{input} ist in dieser Forschung keine Sendung aus der Strom-App. Es ist nichts geschehen.",
|
|
1961
1968
|
"ui.link.sync.was": "Diese Sendung wurde bereits zurückgenommen.",
|
|
@@ -3112,6 +3119,11 @@
|
|
|
3112
3119
|
"ui.check.otherkey.afterrepair.hint": "nach strom repair vereinen in einem eigenen Terminal: strom seal adopt",
|
|
3113
3120
|
"ui.repair.resealed": "neu versiegelt – der Commit außerhalb von strom änderte keinen Eintrag",
|
|
3114
3121
|
"ui.repair.unite": "weiter: die Schlüssel von strom auf diesem Computer vereinen in einem eigenen Terminal: strom seal adopt",
|
|
3122
|
+
"ui.repair.nothing": "nichts zu reparieren – jeder Eintrag stimmt mit seiner Versiegelung überein",
|
|
3123
|
+
"ui.repair.restored": "wiederhergestellt {file}",
|
|
3124
|
+
"ui.repair.removed": "entfernt {file}",
|
|
3125
|
+
"ui.repair.from": "versiegelter Stand: {commit} · Kopien ersetzter Dateien: .strom/quarantine/",
|
|
3126
|
+
"ui.repair.still": "noch {n, plural, one {# Problem} other {# Probleme}} → strom verify",
|
|
3115
3127
|
"ui.error.seal.repair-in-terminal": "die Forschung „{name}“ enthält Arbeit, versiegelt mit dem Schlüssel des strom aus dem Terminal, den ein aus einer App gestartetes strom nicht sieht: nichts wurde repariert",
|
|
3116
3128
|
"ui.error.seal.repair-in-terminal.hint": "in einem eigenen Terminal: strom repair, dann strom seal adopt – es sieht beide Schlüssel und macht nur rückgängig, was außerhalb von strom geschah",
|
|
3117
3129
|
"ui.menu.seal": "Auf einem anderen Computer versiegelt: schreiben hier erst nach der Übernahme (strom seal adopt).",
|
package/assets/method/narrate.md
CHANGED
|
@@ -18,6 +18,11 @@ A story is written from the facts, for the family.
|
|
|
18
18
|
- Write it to a file in notes/, then `strom story set P… --text @notes/story-P….md
|
|
19
19
|
--title "…" --fact E… --fact E… --source S… --note "what is inferred"`: every fact it leans on goes
|
|
20
20
|
in --fact, every source of its background in --source. A couple's story goes on the family (F…).
|
|
21
|
+
- A couple's story tells their life together: the wedding, the household, the
|
|
22
|
+
homes, the children, the widowhood. Each one's life before and after it is
|
|
23
|
+
their own story's — referred to in a sentence, never told again.
|
|
24
|
+
- A person whose couple has a story: the marriage in one sentence pointing to
|
|
25
|
+
it. What a story told already stays; it is never shortened for a couple's.
|
|
21
26
|
- It stays a draft; when the user says it is right: `strom story approve P…`
|
|
22
27
|
(the Strom app asks them too). An approved story is locked — the user liked
|
|
23
28
|
it: written again (more facts, a better reading), the new version waits
|
package/dist/brief/brief.js
CHANGED
|
@@ -294,6 +294,46 @@ function familyBlock(tree, f, short = false) {
|
|
|
294
294
|
out.push(` note: ${noteStart(n.text, short ? NOTE_LINE : NOTE_SHOWN)}`);
|
|
295
295
|
return out;
|
|
296
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* What the other stories tell already, for a story to write (spec 027 K6.3–K6.4): a couple's — the partners' own
|
|
299
|
+
* stories, to be referred to and not told again; a person's — the couples' stories of theirs, the marriage in a
|
|
300
|
+
* sentence pointing to it, and what the person's story told already never shortened for it.
|
|
301
|
+
*/
|
|
302
|
+
function storiesTold(tree, task) {
|
|
303
|
+
const subject = task.subject.map((id) => tree.get(id)).find((r) => r?.type === "family" || r?.type === "person");
|
|
304
|
+
if (!subject)
|
|
305
|
+
return undefined;
|
|
306
|
+
if (subject.type === "family") {
|
|
307
|
+
const partners = subject.partners.map((x) => tree.get(x)).filter((x) => !!x && !x.retracted);
|
|
308
|
+
const text = [
|
|
309
|
+
`## The couple's story (${subject.id}): their life together alone — the wedding, the household, the homes, the children, the widowhood`,
|
|
310
|
+
"- each one's life before the wedding and after it is their own story's: referred to in a sentence, never told again",
|
|
311
|
+
"- every statement on a recorded fact (--fact), as in a person's story",
|
|
312
|
+
];
|
|
313
|
+
// who has no story comes before the stories: a line under a partner's story would read as part of it
|
|
314
|
+
for (const p of partners)
|
|
315
|
+
if (!(p.story?.draft ?? p.story))
|
|
316
|
+
text.push(`- ${label(p)}: no story of their own yet — their life outside the marriage stays out of this one too`);
|
|
317
|
+
for (const p of partners) {
|
|
318
|
+
const st = p.story?.draft ?? p.story;
|
|
319
|
+
if (!st)
|
|
320
|
+
continue;
|
|
321
|
+
text.push(`### The story of ${label(p)}${p.story?.draft ? " (its new version, waiting for the user)" : ` (${p.story.status})`}${st.title ? `: ${st.title}` : ""}`, st.text.trim());
|
|
322
|
+
}
|
|
323
|
+
return { name: "stories told", first: true, pointer: `strom story show ${partners.map((p) => p.id).join(" · strom story show ")}`, text: text.join("\n") };
|
|
324
|
+
}
|
|
325
|
+
const couples = familiesAsPartner(tree, subject.id).filter((f) => !f.retracted && f.story);
|
|
326
|
+
if (!couples.length)
|
|
327
|
+
return undefined;
|
|
328
|
+
const text = [`## The couples' stories of ${label(subject)}: told already`];
|
|
329
|
+
for (const f of couples) {
|
|
330
|
+
const others = f.partners.filter((x) => x !== subject.id).map((x) => tree.get(x)).filter((x) => !!x).map(label);
|
|
331
|
+
text.push(`- ${f.id}${others.length ? ` with ${others.join(", ")}` : ""} has a story of its own (strom story show ${f.id}): the marriage in one sentence pointing to it — their life together not told again`);
|
|
332
|
+
}
|
|
333
|
+
if (subject.story)
|
|
334
|
+
text.push("- what the story told already stays — never shortened because of a couple's story");
|
|
335
|
+
return { name: "stories told", first: true, pointer: `strom story show ${couples.map((f) => f.id).join(" · strom story show ")}`, text: text.join("\n") };
|
|
336
|
+
}
|
|
297
337
|
/** A small text file of an input, as strom input show gives it; unreadable: nothing. */
|
|
298
338
|
function readSmall(file) {
|
|
299
339
|
try {
|
|
@@ -1299,6 +1339,9 @@ export function buildBrief(tree, opts) {
|
|
|
1299
1339
|
const ofTime = storyHistoryOn && task.level === "narrate" ? storyHistorySection(tree, task, opts.shared) : undefined;
|
|
1300
1340
|
if (ofTime)
|
|
1301
1341
|
sections.push(ofTime);
|
|
1342
|
+
const told = task?.level === "narrate" ? storiesTold(tree, task) : undefined;
|
|
1343
|
+
if (told)
|
|
1344
|
+
sections.push(told);
|
|
1302
1345
|
// 8 bis. the search beyond the registers: its budget of pages, printed pages for the user's model for print where the
|
|
1303
1346
|
// agent hands work to another model, and what the research has written of the person's places already
|
|
1304
1347
|
if (task?.origin === STORY_SOURCES_ORIGIN) {
|
package/dist/cli/menu-links.js
CHANGED
|
@@ -442,7 +442,9 @@ export async function approveStory(ctx, run, lang, root, person, who, how = "fin
|
|
|
442
442
|
if (st.status === "final")
|
|
443
443
|
return stop(ctx, t("ui.link.story.final", { person: who }));
|
|
444
444
|
out(ctx, lines(st.title ? `„${st.title}“` : undefined, "", st.text.replace(/\*\*/g, ""), ""));
|
|
445
|
-
|
|
445
|
+
// a couple's story said as the couple's, never as a person's
|
|
446
|
+
const what = person.type === "family" ? t("ui.link.what.story.couple", { couple: who }) : t("ui.link.what.story", { person: who });
|
|
447
|
+
if (!(await asks(ctx, lang, root, what)))
|
|
446
448
|
return false;
|
|
447
449
|
if ((await run(["story", "approve", person.id], true)) !== 0)
|
|
448
450
|
return false;
|
package/dist/cli/ui.js
CHANGED
|
@@ -1930,6 +1930,7 @@ export const UI = {
|
|
|
1930
1930
|
"ui.link.story.none": "{person} has no story yet. Nothing was done.",
|
|
1931
1931
|
"ui.link.story.final": "The story of {person} is approved already.",
|
|
1932
1932
|
"ui.link.what.story": "approve the story of {person} as it is (it will no longer be a draft).",
|
|
1933
|
+
"ui.link.what.story.couple": "approve the story of the couple {couple} as it is (it will no longer be a draft).",
|
|
1933
1934
|
"ui.link.story.done": "✓ The story is approved. Open the research in the Strom app again and it is there without the draft label.",
|
|
1934
1935
|
"ui.link.sync.none": "{input} is not a sending from the Strom app in this research. Nothing was done.",
|
|
1935
1936
|
"ui.link.sync.was": "This sending was taken back already.",
|
|
@@ -2804,6 +2805,11 @@ export const UI = {
|
|
|
2804
2805
|
"ui.check.otherkey.afterrepair.hint": "after strom repair, united in a terminal of its own: strom seal adopt",
|
|
2805
2806
|
"ui.repair.resealed": "sealed again — the commit made outside strom changed no record",
|
|
2806
2807
|
"ui.repair.unite": "next: the keys of strom on this computer united in a terminal of its own: strom seal adopt",
|
|
2808
|
+
"ui.repair.nothing": "nothing to repair — every record matches its seal",
|
|
2809
|
+
"ui.repair.restored": "restored {file}",
|
|
2810
|
+
"ui.repair.removed": "removed {file}",
|
|
2811
|
+
"ui.repair.from": "sealed state: {commit} · copies of replaced files: .strom/quarantine/",
|
|
2812
|
+
"ui.repair.still": "still {n, plural, one {# problem} other {# problems}} → strom verify",
|
|
2807
2813
|
"ui.error.seal.repair-in-terminal": "\"{name}\" holds work sealed with the key of the terminal's strom, which a strom started by an app cannot see: nothing repaired",
|
|
2808
2814
|
"ui.error.seal.repair-in-terminal.hint": "in a terminal of its own: strom repair, then strom seal adopt — it sees both keys and undoes only what was done outside strom",
|
|
2809
2815
|
"ui.menu.seal": "Sealed on another computer: writing here only once taken over (strom seal adopt).",
|
package/dist/commands/checks.js
CHANGED
|
@@ -8,9 +8,8 @@ import { counted, lines, table } from "../cli/format.js";
|
|
|
8
8
|
import { check, SHARED_MISSING, sharedMissing } from "../core/check.js";
|
|
9
9
|
import { guard } from "../core/guard.js";
|
|
10
10
|
import { unprovenPeople } from "../core/review.js";
|
|
11
|
-
import { AFTER_REPAIR_HINT, NOT_DOWNLOADED, OTHER_KEY, SEALED_PATHS, notDownloaded, notDownloadedError, otherKeyError, verifyFast, verifyFull } from "../core/integrity.js";
|
|
12
|
-
import { otherKeys
|
|
13
|
-
import { settingsHidden } from "../core/oldsettings.js";
|
|
11
|
+
import { AFTER_REPAIR_HINT, NOT_DOWNLOADED, OTHER_KEY, REPAIR_IN_TERMINAL_HINT, SEALED_PATHS, lastSealed, notDownloaded, notDownloadedError, otherKeyError, verifyFast, verifyFull } from "../core/integrity.js";
|
|
12
|
+
import { otherKeys } from "../core/seal.js";
|
|
14
13
|
import { adoptSeal, keysToUnite } from "../cli/seal.js";
|
|
15
14
|
import { NeedsConsentError, StromError } from "../core/errors.js";
|
|
16
15
|
import * as git from "../core/git.js";
|
|
@@ -157,18 +156,14 @@ function repair(tree) {
|
|
|
157
156
|
const key = tree.requireKey();
|
|
158
157
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
159
158
|
const head = git.headInfo(tree.root);
|
|
160
|
-
// the newest commit any key of this computer sealed
|
|
161
|
-
//
|
|
162
|
-
const
|
|
163
|
-
const above = [];
|
|
164
|
-
const sealed = git.findCommit(tree.root, (t, m) => keys.some((k) => verifyCommitSeal(k, t, m)) || (above.push(m), false));
|
|
159
|
+
// the newest commit any key of this computer sealed: only what was done outside strom undone, the other key's work
|
|
160
|
+
// kept — strom seal adopt unites them after
|
|
161
|
+
const { sealed, inTerminal } = lastSealed(tree, key);
|
|
165
162
|
if (!sealed)
|
|
166
163
|
throw new StromError("no sealed commit found — nothing to restore from", { hint: "ask the user; the history may have been rewritten" });
|
|
167
|
-
|
|
168
|
-
// sight — never undone from here, the terminal's strom sees both keys
|
|
169
|
-
if (above.some((m) => readCommitSeal(m) !== undefined) && settingsHidden(tree.env))
|
|
164
|
+
if (inTerminal)
|
|
170
165
|
throw new StromError(`"${tree.config.name}" holds work sealed with the key of the terminal's strom, which this strom (started by an app packaged for Windows) does not see — nothing repaired`, {
|
|
171
|
-
hint:
|
|
166
|
+
hint: REPAIR_IN_TERMINAL_HINT,
|
|
172
167
|
code: "seal.repair-in-terminal",
|
|
173
168
|
params: { name: tree.config.name },
|
|
174
169
|
});
|
|
@@ -230,8 +225,8 @@ register({
|
|
|
230
225
|
const text = r.restored.length + r.quarantined.length === 0
|
|
231
226
|
? r.resealed
|
|
232
227
|
? lines(ui(lang, "ui.repair.resealed"), next)
|
|
233
|
-
: lines("nothing
|
|
234
|
-
: lines(...r.restored.map((
|
|
228
|
+
: lines(ui(lang, "ui.repair.nothing"), next)
|
|
229
|
+
: lines(...r.restored.map((file) => ui(lang, "ui.repair.restored", { file })), ...r.quarantined.map((file) => ui(lang, "ui.repair.removed", { file })), ui(lang, "ui.repair.from", { commit: r.from.slice(0, 8) }), after.length ? ui(lang, "ui.repair.still", { n: after.length }) : next);
|
|
235
230
|
return { text, data: { ...r, ok: after.length === 0, ...(unite ? { unite: "strom seal adopt" } : {}) }, exitCode: after.length ? 1 : 0 };
|
|
236
231
|
},
|
|
237
232
|
}, {
|
package/dist/commands/guide.js
CHANGED
|
@@ -253,7 +253,8 @@ STORIES OF THE ANCESTORS (the setting stories — on by default; strom shows it)
|
|
|
253
253
|
task: write the story for the family book — plain words, the research language, every statement on a
|
|
254
254
|
recorded fact (strom story set P… --text @notes/story-P….md --title "…" --fact E… …; paragraphs,
|
|
255
255
|
"## " subheadings, "- " bullets, **bold**, *italic* — nothing else shows in the app). More facts later: it
|
|
256
|
-
proposes adding to it.
|
|
256
|
+
proposes adding to it. Once one of a couple has a story, the couple's (strom story set F…): their life
|
|
257
|
+
together alone, the partners' stories referred to, not retold. Stories wait behind all the research; working alone
|
|
257
258
|
(strom run), every few sessions one comes first. In a conversation the user leads: write one when they
|
|
258
259
|
want it, or offer it when the research has a pause. A story is a draft until the user approves it (strom story approve P…; the Strom app asks it too). When strom says to tell
|
|
259
260
|
the user (once, not in every conversation): in a sentence, and that they may say no (strom config set
|
package/dist/core/integrity.js
CHANGED
|
@@ -484,6 +484,19 @@ export function writableUnasked(tree) {
|
|
|
484
484
|
return false;
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* The newest commit any key of this computer sealed (strom of an app packaged for Windows and of a terminal each had
|
|
489
|
+
* their own), and whether strom repair is the terminal's: started by such an app, a commit above it with strom's seal may
|
|
490
|
+
* be the terminal's key out of its sight — never undone from here, the terminal's strom sees both keys.
|
|
491
|
+
*/
|
|
492
|
+
export function lastSealed(tree, key) {
|
|
493
|
+
const keys = [key, ...otherKeys(tree.env, tree.config.id, key).map((o) => o.key)];
|
|
494
|
+
const above = [];
|
|
495
|
+
const sealed = git.findCommit(tree.root, (t, m) => keys.some((k) => verifyCommitSeal(k, t, m)) || (above.push(m), false));
|
|
496
|
+
return { sealed, inTerminal: !!sealed && above.some((m) => readCommitSeal(m) !== undefined) && settingsHidden(tree.env) };
|
|
497
|
+
}
|
|
498
|
+
/** What strom repair started by an app packaged for Windows says instead (lastSealed inTerminal). */
|
|
499
|
+
export const REPAIR_IN_TERMINAL_HINT = "the user runs in their terminal: strom repair, then strom seal adopt (it sees both keys and undoes only what was done outside strom)";
|
|
487
500
|
/** Refuse to write on top of data that was changed outside strom. */
|
|
488
501
|
export function assertIntact(tree) {
|
|
489
502
|
const { findings } = verifyFast(tree);
|
|
@@ -498,8 +511,15 @@ export function assertIntact(tree) {
|
|
|
498
511
|
// files only in iCloud: downloaded first — what else is wrong is judged then
|
|
499
512
|
if (errors.some((f) => f.code === NOT_DOWNLOADED))
|
|
500
513
|
throw notDownloadedError(notDownloaded(tree.root));
|
|
514
|
+
// with another key of this computer: the one way strom check says (oneWay); started by an app packaged for Windows
|
|
515
|
+
// above the terminal's work, strom repair in the terminal — never first the app's own, which refuses (found 2026-10-30)
|
|
516
|
+
const way = errors.some((f) => f.code === OTHER_KEY)
|
|
517
|
+
? errors.find((f) => f.code !== OTHER_KEY && f.hint?.endsWith("then strom seal adopt"))?.hint
|
|
518
|
+
: tree.key && errors.some((f) => f.code === "foreign-commit") && lastSealed(tree, tree.key).inTerminal
|
|
519
|
+
? REPAIR_IN_TERMINAL_HINT
|
|
520
|
+
: undefined;
|
|
501
521
|
throw new StromError(`data was changed outside strom (${counted(errors.length, "problem")}) — refusing to write`, {
|
|
502
|
-
hint:
|
|
522
|
+
hint: `strom verify (details)\n${way ?? "strom repair (restore the last sealed state)"}`,
|
|
503
523
|
details: errors,
|
|
504
524
|
});
|
|
505
525
|
}
|
package/dist/core/oldsettings.js
CHANGED
|
@@ -12,6 +12,18 @@ import path from "node:path";
|
|
|
12
12
|
import { configDir, userHome } from "./paths.js";
|
|
13
13
|
import { writesPrivately } from "./packaged.js";
|
|
14
14
|
import { installation } from "./self.js";
|
|
15
|
+
/**
|
|
16
|
+
* %APPDATA%\strom alone, as this process sees it (oldSettingsDirs' first), without looking through the packaged apps:
|
|
17
|
+
* where an older strom looks for its keys.
|
|
18
|
+
*/
|
|
19
|
+
export function oldSettingsDir(env, platform = process.platform, inst = installation()) {
|
|
20
|
+
if (inst.kind === "source" && env.STROM_TEST_OLD_SETTINGS !== undefined)
|
|
21
|
+
return env.STROM_TEST_OLD_SETTINGS.split(path.delimiter).filter(Boolean)[0];
|
|
22
|
+
if (env.STROM_CONFIG_DIR || platform !== "win32")
|
|
23
|
+
return undefined;
|
|
24
|
+
const home = env.USERPROFILE || userHome(env);
|
|
25
|
+
return path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), "strom");
|
|
26
|
+
}
|
|
15
27
|
/**
|
|
16
28
|
* The folders strom kept its settings in before, as this process sees them: %APPDATA%\strom first (in a packaged app
|
|
17
29
|
* its private copy where there is one, else the real one), then each packaged app's copy, read by its path. None with
|
|
@@ -21,10 +33,10 @@ import { installation } from "./self.js";
|
|
|
21
33
|
export function oldSettingsDirs(env, platform = process.platform, inst = installation()) {
|
|
22
34
|
if (inst.kind === "source" && env.STROM_TEST_OLD_SETTINGS !== undefined)
|
|
23
35
|
return env.STROM_TEST_OLD_SETTINGS.split(path.delimiter).filter(Boolean);
|
|
24
|
-
|
|
36
|
+
const roaming = oldSettingsDir(env, platform, inst);
|
|
37
|
+
if (!roaming)
|
|
25
38
|
return [];
|
|
26
39
|
const home = env.USERPROFILE || userHome(env);
|
|
27
|
-
const roaming = path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), "strom");
|
|
28
40
|
const packages = path.join(env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "Packages");
|
|
29
41
|
let apps = [];
|
|
30
42
|
try {
|
package/dist/core/phrases.js
CHANGED
|
@@ -35,6 +35,12 @@ export const PHRASES = {
|
|
|
35
35
|
"story.where": "the facts recorded (strom person show {id})",
|
|
36
36
|
"story.why": "{count, plural, one {# fact from records tells} other {# facts from records tell}} the life (generation {generation} of {research}); the family reads it in the Strom app's family book",
|
|
37
37
|
"story.done": "strom story set {id} with every fact it rests on (--fact) — a draft until the user approves it",
|
|
38
|
+
"story.couple.what": "Write the story of the couple {a} and {b}",
|
|
39
|
+
"story.couple.more.what": "Add to the story of the couple {a} and {b}: {count, plural, one {# new fact} other {# new facts}}",
|
|
40
|
+
"story.couple.where": "the couple's facts recorded (strom family show {id})",
|
|
41
|
+
"story.couple.partner": "the story of {name}, told already: referred to, not retold (strom story show {id})",
|
|
42
|
+
"story.couple.why": "{count, plural, one {# fact from records tells} other {# facts from records tell}} their life together (generation {generation} of {research}); each one's own life is in their own story",
|
|
43
|
+
"story.couple.done": "strom story set {id} with every fact it rests on (--fact): their life together alone — the wedding, the household, the homes, the children, the widowhood — a draft until the user approves it",
|
|
38
44
|
"story.sources.what": "Beyond the registers, for the story of {name}: newspapers, directories, military, land and court records, graves, the history of the place",
|
|
39
45
|
"story.sources.where": "the digitised newspapers, books and archives of the country and time; the web (indexes and trees are leads only)",
|
|
40
46
|
"story.sources.why": "the story is to tell more than the registers do — every word still on a record; then strom proposes the story",
|
package/dist/core/queue.js
CHANGED
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
import { parentsOf } from "./people.js";
|
|
25
25
|
import { taskRecordsets } from "./frontier.js";
|
|
26
26
|
import { now } from "./tree.js";
|
|
27
|
-
import {
|
|
28
|
-
import { directionOf, scopes } from "./directions.js";
|
|
27
|
+
import { aboutPeople, directionOf, scopes } from "./directions.js";
|
|
29
28
|
import { offTree } from "./kin.js";
|
|
30
29
|
import { forStory } from "./stories.js";
|
|
31
30
|
import { HYPOTHESIS_LINKS_TRIES, linksFirst } from "./hypolinks.js";
|
|
@@ -153,7 +152,8 @@ export function rankTasks(tree, tasks, strategy = "balanced", opts = {}) {
|
|
|
153
152
|
const place = (t) => {
|
|
154
153
|
// a task of no research of its own: the direction its people are in (directions.ts), as the menu and the app say
|
|
155
154
|
const map = linesOf(t.research ?? directionOf(tree, t, all));
|
|
156
|
-
|
|
155
|
+
// a family's task: its partners' (a couple's story)
|
|
156
|
+
const found = [...t.subject, ...aboutPeople(tree, t.subject)].map((s) => map?.get(s)).filter((x) => !!x);
|
|
157
157
|
return found.sort((a, b) => a.gen - b.gen)[0];
|
|
158
158
|
};
|
|
159
159
|
const tasksById = new Map(tree.list("task").map((t) => [t.id, t]));
|
package/dist/core/seal.js
CHANGED
|
@@ -16,7 +16,7 @@ import fs from "node:fs";
|
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
import { configDir } from "./paths.js";
|
|
18
18
|
import { canonicalize } from "./json.js";
|
|
19
|
-
import { oldKeys,
|
|
19
|
+
import { oldKeys, oldSettingsDir } from "./oldsettings.js";
|
|
20
20
|
export function sha256(content) {
|
|
21
21
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
22
22
|
}
|
|
@@ -51,8 +51,10 @@ export function loadKey(env, treeId) {
|
|
|
51
51
|
*/
|
|
52
52
|
export function settleKey(env, treeId, prefer) {
|
|
53
53
|
const own = readKey(keyFile(env, treeId));
|
|
54
|
-
if (own)
|
|
54
|
+
if (own) {
|
|
55
|
+
mirrorKey(env, treeId, own);
|
|
55
56
|
return own;
|
|
57
|
+
}
|
|
56
58
|
const old = oldKeys(env, treeId);
|
|
57
59
|
if (!old.length)
|
|
58
60
|
return undefined;
|
|
@@ -65,8 +67,29 @@ export function settleKey(env, treeId, prefer) {
|
|
|
65
67
|
catch {
|
|
66
68
|
// taken over next time
|
|
67
69
|
}
|
|
70
|
+
mirrorKey(env, treeId, key);
|
|
68
71
|
return key;
|
|
69
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The key also where an older strom looks (Windows: %APPDATA%\strom\keys), made if need be — back on an older version,
|
|
75
|
+
* the research is still sealed here. Only added: a key of its own there already stays (found 2026-10-30: a key taken
|
|
76
|
+
* from a packaged app's copy, never in the folder the terminal's older strom reads).
|
|
77
|
+
*/
|
|
78
|
+
function mirrorKey(env, treeId, key) {
|
|
79
|
+
const old = oldSettingsDir(env);
|
|
80
|
+
if (!old)
|
|
81
|
+
return;
|
|
82
|
+
const file = path.join(old, "keys", `${treeId}.key`);
|
|
83
|
+
if (fs.existsSync(file))
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
87
|
+
fs.writeFileSync(file, key.toString("hex") + "\n", { mode: 0o600, flag: "wx" });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// a key of its own there already: the older strom keeps it
|
|
91
|
+
}
|
|
92
|
+
}
|
|
70
93
|
/** The tree's other keys on this computer: those of the old settings folders that are not `key` (strom seal adopt unites them). */
|
|
71
94
|
export function otherKeys(env, treeId, key) {
|
|
72
95
|
return oldKeys(env, treeId).filter((o) => !o.key.equals(key));
|
|
@@ -80,18 +103,7 @@ export function createKey(env, treeId) {
|
|
|
80
103
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
81
104
|
const key = crypto.randomBytes(32);
|
|
82
105
|
fs.writeFileSync(file, key.toString("hex") + "\n", { mode: 0o600, flag: "wx" });
|
|
83
|
-
|
|
84
|
-
// is still sealed here
|
|
85
|
-
const old = oldSettingsDirs(env)[0];
|
|
86
|
-
if (old) {
|
|
87
|
-
try {
|
|
88
|
-
fs.mkdirSync(path.join(old, "keys"), { recursive: true });
|
|
89
|
-
fs.writeFileSync(path.join(old, "keys", `${treeId}.key`), key.toString("hex") + "\n", { mode: 0o600, flag: "wx" });
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
// a key of its own there already: the older strom keeps it
|
|
93
|
-
}
|
|
94
|
-
}
|
|
106
|
+
mirrorKey(env, treeId, key);
|
|
95
107
|
return key;
|
|
96
108
|
}
|
|
97
109
|
/** Short public fingerprint of a key, stored on each operation. */
|
package/dist/core/stories.js
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
// beyond the registers — newspapers, directories, military, land and court
|
|
7
7
|
// records, graves, the history of the place — so the story can tell more, every
|
|
8
8
|
// word still on a record; the story is proposed when that search is finished.
|
|
9
|
+
// A couple's story too, once one of the two has a story of their own: their life
|
|
10
|
+
// together alone (the wedding, the household, the homes, the children, the
|
|
11
|
+
// widowhood); what the partners' stories tell is referred to, never told again.
|
|
9
12
|
import { researchPeople } from "./frontier.js";
|
|
10
13
|
import { displayName, familiesAsPartner, lifespan } from "./people.js";
|
|
11
14
|
import { phrase } from "./phrases.js";
|
|
@@ -24,6 +27,7 @@ export function forStory(t) {
|
|
|
24
27
|
}
|
|
25
28
|
const FROM_RECORDS = new Set(["proven", "probable"]);
|
|
26
29
|
const BIRTHS = new Set(["BIRT", "CHR", "BAPM"]);
|
|
30
|
+
const MARRIAGES = new Set(["MARR"]);
|
|
27
31
|
const OPEN = new Set(["open", "doing", "parked", "waiting"]);
|
|
28
32
|
const FINISHED = new Set(["done", "dropped"]);
|
|
29
33
|
/** Enough to tell a life: a birth or baptism from a record, and more facts from records. */
|
|
@@ -36,6 +40,23 @@ export function factsFromRecords(tree, p) {
|
|
|
36
40
|
const marriages = familiesAsPartner(tree, p.id).flatMap((f) => f.events.filter((e) => !e.retracted && FROM_RECORDS.has(e.status)));
|
|
37
41
|
return [...own, ...marriages];
|
|
38
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The facts of a couple's life together that records support: the family's own (the wedding, the homes, …) and the
|
|
45
|
+
* birth or baptism of each child — and whether they show the couple (a wedding or a child from a record).
|
|
46
|
+
*/
|
|
47
|
+
export function coupleFacts(tree, f) {
|
|
48
|
+
const own = f.events.filter((e) => !e.retracted && FROM_RECORDS.has(e.status));
|
|
49
|
+
const births = f.children.flatMap((c) => {
|
|
50
|
+
const child = tree.get(c.person);
|
|
51
|
+
const birth = child && !child.retracted ? child.events.find((e) => !e.retracted && FROM_RECORDS.has(e.status) && BIRTHS.has(e.kind)) : undefined;
|
|
52
|
+
return birth ? [birth] : [];
|
|
53
|
+
});
|
|
54
|
+
return { facts: [...own, ...births], shown: births.length > 0 || own.some((e) => MARRIAGES.has(e.kind)) };
|
|
55
|
+
}
|
|
56
|
+
/** Has the person a story of their own — written, or the task of it done? */
|
|
57
|
+
function toldAlone(p, narrate) {
|
|
58
|
+
return !!p.story || narrate.some((t) => t.state === "done" && t.subject.includes(p.id));
|
|
59
|
+
}
|
|
39
60
|
/** The stories strom proposes to write (or add to) for the ancestors of a research. */
|
|
40
61
|
export function storyProposals(tree, research) {
|
|
41
62
|
const lang = tree.lang;
|
|
@@ -43,29 +64,18 @@ export function storyProposals(tree, research) {
|
|
|
43
64
|
const narrate = tasks.filter((t) => t.level === "narrate");
|
|
44
65
|
const sources = new Settings(tree.env, {}).storySources(tree.config);
|
|
45
66
|
const out = [];
|
|
46
|
-
|
|
67
|
+
const people = researchPeople(tree, research);
|
|
68
|
+
for (const [id, generation] of people) {
|
|
47
69
|
const p = tree.get(id);
|
|
48
70
|
if (!p || p.retracted)
|
|
49
71
|
continue;
|
|
50
72
|
const facts = factsFromRecords(tree, p);
|
|
51
73
|
if (facts.length < STORY_MIN_FACTS || !facts.some((e) => BIRTHS.has(e.kind)))
|
|
52
74
|
continue;
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
continue;
|
|
56
|
-
// The latest version written: a new one waiting beside an approved story, else the story.
|
|
57
|
-
const latest = p.story?.draft ?? p.story;
|
|
58
|
-
// Dropped since the story was written (or with none): that was the answer — not again until the story changes.
|
|
59
|
-
const since = latest?.at ?? "";
|
|
60
|
-
if (mine.some((t) => t.state === "dropped" && t.updated >= since))
|
|
61
|
-
continue;
|
|
62
|
-
// Done, yet no story: somebody decided so; the user can add the task by hand.
|
|
63
|
-
if (!p.story && mine.some((t) => FINISHED.has(t.state)))
|
|
75
|
+
const fresh = due(p, facts, narrate);
|
|
76
|
+
if (!fresh)
|
|
64
77
|
continue;
|
|
65
78
|
const name = `${displayName(p)}${lifespan(p) ? ` (${lifespan(p)})` : ""}`;
|
|
66
|
-
const fresh = latest ? facts.filter((e) => !latest.facts.includes(e.id)) : facts;
|
|
67
|
-
if (p.story && fresh.length < STORY_NEW_FACTS)
|
|
68
|
-
continue;
|
|
69
79
|
// First, once, the search beyond the registers; the story waits while it is open, and comes when it is finished
|
|
70
80
|
// (done, or dropped: the user's answer about the search, not about the story).
|
|
71
81
|
if (sources) {
|
|
@@ -98,8 +108,60 @@ export function storyProposals(tree, research) {
|
|
|
98
108
|
research: research.id,
|
|
99
109
|
});
|
|
100
110
|
}
|
|
111
|
+
// The couples of the research, once one of the two has a story of their own: their life together, the partners'
|
|
112
|
+
// stories given to the agent as what is told already. The search beyond the registers was each partner's.
|
|
113
|
+
for (const f of tree.list("family")) {
|
|
114
|
+
if (f.retracted || f.noCouple || f.partners.length !== 2 || !f.partners.some((x) => people.has(x)))
|
|
115
|
+
continue;
|
|
116
|
+
const partners = f.partners.map((x) => tree.get(x));
|
|
117
|
+
if (partners.some((x) => !x || x.retracted))
|
|
118
|
+
continue;
|
|
119
|
+
const [a, b] = partners;
|
|
120
|
+
if (!toldAlone(a, narrate) && !toldAlone(b, narrate))
|
|
121
|
+
continue;
|
|
122
|
+
const { facts, shown } = coupleFacts(tree, f);
|
|
123
|
+
if (!shown || facts.length < STORY_MIN_FACTS)
|
|
124
|
+
continue;
|
|
125
|
+
const fresh = due(f, facts, narrate);
|
|
126
|
+
if (!fresh)
|
|
127
|
+
continue;
|
|
128
|
+
const generation = Math.min(...f.partners.map((x) => people.get(x) ?? Infinity));
|
|
129
|
+
const names = { a: displayName(a), b: displayName(b) };
|
|
130
|
+
out.push({
|
|
131
|
+
level: "narrate",
|
|
132
|
+
priority: 1, // like a person's: after the research
|
|
133
|
+
what: phrase(lang, f.story ? "story.couple.more.what" : "story.couple.what", { ...names, count: fresh.length }),
|
|
134
|
+
where: [phrase(lang, "story.couple.where", { id: f.id }), ...[a, b].filter((x) => x.story).map((x) => phrase(lang, "story.couple.partner", { name: displayName(x), id: x.id }))],
|
|
135
|
+
why: phrase(lang, "story.couple.why", { count: facts.length, generation, research: research.name }),
|
|
136
|
+
doneWhen: phrase(lang, "story.couple.done", { id: f.id }),
|
|
137
|
+
subject: [f.id],
|
|
138
|
+
research: research.id,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
101
141
|
return out;
|
|
102
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* The new facts a story of the person or couple is due for — all of them before it is written — or undefined: its task
|
|
145
|
+
* open, dropped since the story was written (that was the answer), done with no story, or too few new since.
|
|
146
|
+
*/
|
|
147
|
+
function due(rec, facts, narrate) {
|
|
148
|
+
const mine = narrate.filter((t) => t.subject.includes(rec.id));
|
|
149
|
+
if (mine.some((t) => OPEN.has(t.state)))
|
|
150
|
+
return undefined;
|
|
151
|
+
// The latest version written: a new one waiting beside an approved story, else the story.
|
|
152
|
+
const latest = rec.story?.draft ?? rec.story;
|
|
153
|
+
// Dropped since the story was written (or with none): that was the answer — not again until the story changes.
|
|
154
|
+
const since = latest?.at ?? "";
|
|
155
|
+
if (mine.some((t) => t.state === "dropped" && t.updated >= since))
|
|
156
|
+
return undefined;
|
|
157
|
+
// Done, yet no story: somebody decided so; the user can add the task by hand.
|
|
158
|
+
if (!rec.story && mine.some((t) => FINISHED.has(t.state)))
|
|
159
|
+
return undefined;
|
|
160
|
+
const fresh = latest ? facts.filter((e) => !latest.facts.includes(e.id)) : facts;
|
|
161
|
+
if (rec.story && fresh.length < STORY_NEW_FACTS)
|
|
162
|
+
return undefined;
|
|
163
|
+
return fresh;
|
|
164
|
+
}
|
|
103
165
|
/** The new versions of approved stories that wait for the user (the lock): whose, and since when. */
|
|
104
166
|
export function storiesToApprove(tree) {
|
|
105
167
|
return [...tree.list("person"), ...tree.list("family")]
|
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.23.0-beta.
|
|
32
|
+
export const VERSION = "1.23.0-beta.4";
|
|
33
33
|
const GITIGNORE = `# Strom Research — local state and large files are not versioned
|
|
34
34
|
.strom/
|
|
35
35
|
/media/
|