strom-research 1.24.0-beta.1 → 1.24.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.cs.md +8 -5
- package/CHANGELOG.de.md +9 -5
- package/CHANGELOG.md +9 -5
- package/assets/lang/cs.json +12 -0
- package/assets/lang/de.json +12 -0
- package/dist/cli/ui.js +8 -0
- package/dist/commands/output.js +5 -2
- package/dist/commands/sync.js +6 -0
- package/dist/core/changelog.js +13 -0
- package/dist/core/import.js +11 -2
- package/dist/core/integrity.js +5 -0
- package/dist/core/lineages.js +147 -0
- package/dist/core/live.js +11 -5
- package/dist/core/phrases.js +4 -0
- package/dist/core/stromapp.js +20 -0
- package/dist/core/sync.js +43 -1
- package/dist/core/tree.js +1 -1
- package/dist/gedcom/export.js +14 -1
- package/dist/gedcom/validate.js +4 -2
- package/package.json +2 -2
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.24.0
|
|
9
9
|
|
|
10
|
-
*Připravuje se: bety do 1.24.0-beta.
|
|
10
|
+
*Připravuje se: bety do 1.24.0-beta.2. Oddíl bude úplný s vydáním.*
|
|
11
11
|
|
|
12
12
|
- **Místa zmapovaná jednou pro všechny výzkumy na tomto počítači.** Místo, které jiný výzkum tady už přiřadil k regionům
|
|
13
13
|
dějin, se převezme, když jde jistě o totéž místo (stejný název a stejné souřadnice, farnost nebo okres) a všechny
|
|
@@ -19,10 +19,12 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
19
19
|
- **Stránka každého skenu v aplikaci Strom.** Soubor pro Strom dává teď každému prameni stránku jeho skenu v online
|
|
20
20
|
archivu: stránku jeho prvního obrázku, jinak vlastní adresu pramene. Aplikace Strom ji nabídne u výřezů, které soubor
|
|
21
21
|
vynechává, a u pramenů bez výřezu. Jen veřejná webová stránka, nikdy adresa fondu; standardní soubor GEDCOM nechává
|
|
22
|
-
adresu v poznámce.
|
|
23
|
-
|
|
22
|
+
adresu v poznámce. Datum verze výzkumu v souboru pro Strom nese i čas, takže aplikace ukáže den, kdy vznikla, i kolem
|
|
23
|
+
půlnoci.
|
|
24
|
+
- **Číslo domu, věk a období tak, jak byly zapsány.** Slovo pro číslo domu („No.“, „čp.“) se dává jen před samotné číslo;
|
|
24
25
|
ulice s číslem jde do souboru tak, jak je. Věk „05y“ se čte jako 5 let. Obojí se z aplikace Strom vrací stejné,
|
|
25
|
-
není to úprava uživatele.
|
|
26
|
+
není to úprava uživatele. Období, které vede aplikace Strom („1871/1882“, nebo otevřené na jednom konci), přijde do
|
|
27
|
+
výzkumu jako od … do …, nikdy jako datum někde mezi nimi.
|
|
26
28
|
- **Otázka na dějiny říká, kde platí.** Zapnutí nebo vypnutí událostí dějin platí pro všechny výzkumy na tomto
|
|
27
29
|
počítači; otázka to říká, i jak to změnit jen pro jeden výzkum.
|
|
28
30
|
- **Slabý nebo odmítnutý model agenta hlásí doctor.** Když výzkum nemá zvolený model a vlastní výchozí model agenta je
|
|
@@ -31,7 +33,8 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
31
33
|
- **strom z npm drží svůj kanál.** strom nainstalovaný přes npm zůstává na kanálu, ze kterého byl nainstalován, i po
|
|
32
34
|
vydání; kanál zvolený přes `strom update --channel` se pro něj také pamatuje.
|
|
33
35
|
- **Příkaz, který selže, vrátí jen své.** Příkaz, který cestou uložil a pak selhal, vrátí jen to, co přišlo po jeho
|
|
34
|
-
uložení, takže výzkum nikdy nevypadá změněný mimo strom.
|
|
36
|
+
uložení, takže výzkum nikdy nevypadá změněný mimo strom. Když se zápis odmítne, protože se výzkum změnil mimo strom,
|
|
37
|
+
člověk to čte v jazyce výzkumu i s cestou, která sedí; agent to dál čte anglicky.
|
|
35
38
|
|
|
36
39
|
## 1.23.0
|
|
37
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.24.0
|
|
9
9
|
|
|
10
|
-
*In Vorbereitung: die Betas bis 1.24.0-beta.
|
|
10
|
+
*In Vorbereitung: die Betas bis 1.24.0-beta.2. Der Abschnitt ist mit dem Release vollständig.*
|
|
11
11
|
|
|
12
12
|
- **Orte einmal zugeordnet für jede Forschung auf diesem Computer.** Ein Ort, den eine andere Forschung hier schon den
|
|
13
13
|
Regionen der Geschichte zugeordnet hat, wird übernommen, wenn es sicher derselbe Ort ist (derselbe Name und dieselben
|
|
@@ -21,10 +21,12 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
21
21
|
- **Die Seite jedes Scans in der Strom-App.** Die Strom-Datei gibt jetzt jeder Quelle die Seite ihres Scans im
|
|
22
22
|
Online-Archiv: die Seite ihres ersten Bildes, sonst die eigene Adresse der Quelle. Die Strom-App verlinkt sie bei den
|
|
23
23
|
Ausschnitten, die die Datei auslässt, und bei Quellen ohne Ausschnitt. Nur eine öffentliche Webseite, nie die Adresse
|
|
24
|
-
eines Bestands; die Standard-GEDCOM-Datei behält die Adresse in der Notiz.
|
|
25
|
-
|
|
24
|
+
eines Bestands; die Standard-GEDCOM-Datei behält die Adresse in der Notiz. Das Datum der Version der
|
|
25
|
+
Forschung in der Strom-Datei trägt auch die Uhrzeit, so zeigt die App den Tag ihrer Entstehung auch um Mitternacht.
|
|
26
|
+
- **Hausnummer, Alter und Zeitraum, wie sie geschrieben wurden.** Das Wort für eine Hausnummer („No.“, „čp.“) steht nur vor
|
|
26
27
|
einer Zahl allein; eine Straße mit Nummer geht so in die Datei, wie sie ist. Ein Alter „05y“ gilt als 5 Jahre.
|
|
27
|
-
Beides kommt aus der Strom-App gleich zurück, keine Änderung des Benutzers.
|
|
28
|
+
Beides kommt aus der Strom-App gleich zurück, keine Änderung des Benutzers. Ein Zeitraum der Strom-App
|
|
29
|
+
(„1871/1882“ oder an einem Ende offen) kommt als von … bis … in die Forschung, nie als ein Datum irgendwo dazwischen.
|
|
28
30
|
- **Die Frage nach der Geschichte sagt, wo sie gilt.** Die Ereignisse der Geschichte ein- oder auszuschalten gilt für
|
|
29
31
|
jede Forschung auf diesem Computer; die Frage sagt es, und wie es sich für eine Forschung allein ändern lässt.
|
|
30
32
|
- **Ein schwaches oder abgelehntes Modell des Agenten meldet der Doctor.** Ist für die Forschung kein Modell gewählt und
|
|
@@ -34,7 +36,9 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
34
36
|
wurde, auch nach einer Veröffentlichung; ein mit `strom update --channel` gewählter Kanal bleibt dafür ebenfalls
|
|
35
37
|
gemerkt.
|
|
36
38
|
- **Ein gescheiterter Befehl nimmt nur das Seine zurück.** Ein Befehl, der unterwegs gespeichert hat und dann scheitert,
|
|
37
|
-
nimmt nur zurück, was nach dieser Speicherung kam, so wirkt die Forschung nie außerhalb von strom geändert.
|
|
39
|
+
nimmt nur zurück, was nach dieser Speicherung kam, so wirkt die Forschung nie außerhalb von strom geändert. Wird das
|
|
40
|
+
Schreiben abgelehnt, weil die Forschung außerhalb von strom geändert wurde, liest die Person das in der Sprache der
|
|
41
|
+
Forschung, mit dem passenden Weg; ein Agent liest es weiter auf Englisch.
|
|
38
42
|
|
|
39
43
|
## 1.23.0
|
|
40
44
|
|
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.24.0
|
|
9
9
|
|
|
10
|
-
*In preparation: the betas up to 1.24.0-beta.
|
|
10
|
+
*In preparation: the betas up to 1.24.0-beta.2. The section is complete with the release.*
|
|
11
11
|
|
|
12
12
|
- **Places mapped once for every research on this computer.** A place another research here has mapped to its
|
|
13
13
|
regions of history is taken over when it is surely the same place (the same name and the same coordinates, parish
|
|
@@ -21,10 +21,12 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
21
21
|
- **The page of each scan in the Strom app.** The Strom file now gives each source the page of its scan in the online
|
|
22
22
|
archive: the page of its first image, else the source's own address. The Strom app links it for the excerpts the
|
|
23
23
|
file leaves out and for the sources without an excerpt. Only a public web page, never a recordset's address; the
|
|
24
|
-
standard GEDCOM file keeps the address in the note.
|
|
25
|
-
|
|
24
|
+
standard GEDCOM file keeps the address in the note. The date of the research's version in the Strom file carries its
|
|
25
|
+
time too, so the app shows the day it was made also around midnight.
|
|
26
|
+
- **A house number, an age and a period as they were written.** The word for a house number ("No.", "čp.") is put only before
|
|
26
27
|
a number standing alone; a street with its number goes into the file as it is. An age of "05y" is read as 5 years.
|
|
27
|
-
Both come back from the Strom app the same, no edit of the user's.
|
|
28
|
+
Both come back from the Strom app the same, no edit of the user's. A period the Strom app keeps ("1871/1882", or
|
|
29
|
+
open at one end) comes into the research as from … to …, never as a date somewhere between.
|
|
28
30
|
- **The question about history says where it holds.** Turning the events of history on or off holds for every
|
|
29
31
|
research on this computer; the question says so, and how to change it for one research alone.
|
|
30
32
|
- **A weak or refused model of the agent said by the doctor.** When no model is chosen for the research and the
|
|
@@ -33,7 +35,9 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
33
35
|
- **strom from npm keeps its channel.** strom installed with npm stays on the channel it was installed from, also
|
|
34
36
|
after a release; a channel chosen with `strom update --channel` is kept for it too.
|
|
35
37
|
- **A failed command takes back only its own.** A command that saved on the way and then failed takes back only what
|
|
36
|
-
came after its save, so the research never looks changed outside strom.
|
|
38
|
+
came after its save, so the research never looks changed outside strom. Where writing is refused because the
|
|
39
|
+
research was changed outside strom, the person reads it in the research's language, with the way out that fits; an
|
|
40
|
+
agent still reads it in English.
|
|
37
41
|
|
|
38
42
|
## 1.23.0
|
|
39
43
|
|
package/assets/lang/cs.json
CHANGED
|
@@ -756,6 +756,10 @@
|
|
|
756
756
|
"log.surname.reject": "Tvary příjmení nejsou jedné rodiny: {forms}",
|
|
757
757
|
"log.surname.unreject": "Tvary příjmení se smějí znovu navrhnout: {forms}",
|
|
758
758
|
"log.surname.undo": "Tvary příjmení vráceny, jak byly",
|
|
759
|
+
"log.lineage.founder": "Zakládá vlastní rod: {who}",
|
|
760
|
+
"log.lineage.nocarry": "Rod nenese: {who}",
|
|
761
|
+
"log.lineage.none": "Rod zase podle výpočtu: {who}",
|
|
762
|
+
"log.lineage.undo": "Rod vrácen, jak byl: {who}",
|
|
759
763
|
"log.participant.fix": "Jména kmotrů a svědků oddělena od poznámek: {n}",
|
|
760
764
|
"log.participant.unfix": "Jména kmotrů a svědků vrácena, jak byla: {n}",
|
|
761
765
|
"fix.appshape.resolution": "Platí údaj výzkumu: {value}",
|
|
@@ -1398,6 +1402,10 @@
|
|
|
1398
1402
|
"ui.sync.do.surname": "zapíše se, jak bylo rozhodnuto v aplikaci",
|
|
1399
1403
|
"ui.sync.do.surname.kept": "zůstává, jak je ve výzkumu",
|
|
1400
1404
|
"ui.sync.do.surname.report": "nic se nemění",
|
|
1405
|
+
"ui.sync.lineage.founder": "{n}. Rod: {name} zakládá vlastní rod",
|
|
1406
|
+
"ui.sync.lineage.nocarry": "{n}. Rod: {name} rod nenese",
|
|
1407
|
+
"ui.sync.lineage.none": "{n}. Rod: {name} zase podle výpočtu",
|
|
1408
|
+
"ui.sync.do.lineage": "zapíše se, jak bylo nastaveno v aplikaci",
|
|
1401
1409
|
"ui.sync.do.part": "doplní se k údaji s odkazem na strom z aplikace",
|
|
1402
1410
|
"ui.sync.do.part.user": "platí úprava z aplikace (verze výzkumu zůstane v historii)",
|
|
1403
1411
|
"ui.sync.do.person.cite": "bude ho citovat jméno osoby (vodítko, dokud výzkum záznam nepřečte)",
|
|
@@ -1757,6 +1765,10 @@
|
|
|
1757
1765
|
"ui.check.streetplaces": "{n, plural, one {# údaj má} few {# údaje mají} other {# údajů má}} v místě ulici nebo číslo domu",
|
|
1758
1766
|
"ui.error.tree.not-downloaded": "z iCloudu ještě nestaženo: {n, plural, one {# soubor} few {# soubory} other {# souborů}} výzkumu (první: {first}) – nic se nezapsalo",
|
|
1759
1767
|
"ui.error.tree.not-downloaded.hint": "otevřít složku výzkumu ve Finderu a soubory stáhnout (nebo složku z iCloud Drive vyjmout), pak spustit znovu – ne strom repair",
|
|
1768
|
+
"ui.error.tree.changed-outside": "data výzkumu se změnila mimo strom ({n, plural, one {# problém} few {# problémy} other {# problémů}}): nic se nezapsalo",
|
|
1769
|
+
"ui.error.tree.changed-outside.hint": "strom verify (podrobnosti)\nstrom repair (vrátí poslední zapečetěný stav)",
|
|
1770
|
+
"ui.error.tree.changed-outside.keys.hint": "strom verify (podrobnosti)\nstrom repair, pak strom seal adopt (vrátí jen to, co se udělalo mimo strom – práce obou klíčů tohoto počítače zůstane)",
|
|
1771
|
+
"ui.error.tree.changed-outside.terminal.hint": "strom verify (podrobnosti)\nve 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",
|
|
1760
1772
|
"ui.export.file": "{file} ({for}) — {persons, plural, one {# osoba} few {# osoby} other {# osob}} · {families, plural, one {# rodina} few {# rodiny} other {# rodin}} · {facts, plural, one {# údaj} few {# údaje} other {# údajů}} · {sources, plural, one {# pramen} few {# prameny} other {# pramenů}}{skipped}",
|
|
1761
1773
|
"ui.export.for.strom": "pro aplikaci Strom",
|
|
1762
1774
|
"ui.export.for.strom.images": "pro aplikaci Strom, s obrázky",
|
package/assets/lang/de.json
CHANGED
|
@@ -756,6 +756,10 @@
|
|
|
756
756
|
"log.surname.reject": "Namensformen keiner gemeinsamen Familie: {forms}",
|
|
757
757
|
"log.surname.unreject": "Namensformen dürfen wieder vorgeschlagen werden: {forms}",
|
|
758
758
|
"log.surname.undo": "Namensformen wiederhergestellt, wie sie waren",
|
|
759
|
+
"log.lineage.founder": "Begründet einen eigenen Stamm: {who}",
|
|
760
|
+
"log.lineage.nocarry": "Trägt keinen Stamm: {who}",
|
|
761
|
+
"log.lineage.none": "Stamm wieder wie berechnet: {who}",
|
|
762
|
+
"log.lineage.undo": "Stamm zurückgesetzt, wie er war: {who}",
|
|
759
763
|
"log.participant.fix": "Namen von Paten und Zeugen von ihren Notizen getrennt: {n}",
|
|
760
764
|
"log.participant.unfix": "Namen von Paten und Zeugen wiederhergestellt, wie sie waren: {n}",
|
|
761
765
|
"fix.appshape.resolution": "Der Wert der Forschung bleibt: {value}",
|
|
@@ -1398,6 +1402,10 @@
|
|
|
1398
1402
|
"ui.sync.do.surname": "übernommen, wie in der App entschieden",
|
|
1399
1403
|
"ui.sync.do.surname.kept": "bleibt, wie die Forschung es hat",
|
|
1400
1404
|
"ui.sync.do.surname.report": "nichts ändert sich",
|
|
1405
|
+
"ui.sync.lineage.founder": "{n}. Stamm: {name} begründet einen eigenen Stamm",
|
|
1406
|
+
"ui.sync.lineage.nocarry": "{n}. Stamm: {name} trägt keinen Stamm",
|
|
1407
|
+
"ui.sync.lineage.none": "{n}. Stamm: {name} wieder wie berechnet",
|
|
1408
|
+
"ui.sync.do.lineage": "übernommen, wie in der App festgelegt",
|
|
1401
1409
|
"ui.sync.do.part": "wird der Angabe hinzugefügt, mit Verweis auf den eigenen Stammbaum",
|
|
1402
1410
|
"ui.sync.do.part.user": "Die eigene Änderung gilt (die der Forschung bleibt in ihrer Geschichte)",
|
|
1403
1411
|
"ui.sync.do.person.cite": "der Name der Person zitiert sie (ein Hinweis, bis die Forschung den Eintrag liest)",
|
|
@@ -1757,6 +1765,10 @@
|
|
|
1757
1765
|
"ui.check.streetplaces": "{n, plural, one {# Angabe hat} other {# Angaben haben}} eine Straße oder eine Nummer im Ort",
|
|
1758
1766
|
"ui.error.tree.not-downloaded": "noch nicht aus iCloud geladen: {n, plural, one {# Datei} other {# Dateien}} der Forschung (die erste: {first}) – nichts wurde geschrieben",
|
|
1759
1767
|
"ui.error.tree.not-downloaded.hint": "den Ordner der Forschung im Finder öffnen und die Dateien laden (oder den Ordner aus iCloud Drive herausnehmen), dann erneut ausführen – nicht strom repair",
|
|
1768
|
+
"ui.error.tree.changed-outside": "die Daten der Forschung wurden außerhalb von strom geändert ({n, plural, one {# Problem} other {# Probleme}}): nichts wurde geschrieben",
|
|
1769
|
+
"ui.error.tree.changed-outside.hint": "strom verify (Einzelheiten)\nstrom repair (stellt den letzten versiegelten Stand wieder her)",
|
|
1770
|
+
"ui.error.tree.changed-outside.keys.hint": "strom verify (Einzelheiten)\nstrom repair, dann strom seal adopt (macht nur rückgängig, was außerhalb von strom geschah – die Arbeit beider Schlüssel dieses Computers bleibt)",
|
|
1771
|
+
"ui.error.tree.changed-outside.terminal.hint": "strom verify (Einzelheiten)\nin 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",
|
|
1760
1772
|
"ui.export.file": "{file} ({for}) — {persons, plural, one {# Person} other {# Personen}} · {families, plural, one {# Familie} other {# Familien}} · {facts, plural, one {# Angabe} other {# Angaben}} · {sources, plural, one {# Quelle} other {# Quellen}}{skipped}",
|
|
1761
1773
|
"ui.export.for.strom": "für die Strom-App",
|
|
1762
1774
|
"ui.export.for.strom.images": "für die Strom-App, mit Bildern",
|
package/dist/cli/ui.js
CHANGED
|
@@ -1357,6 +1357,10 @@ export const UI = {
|
|
|
1357
1357
|
"ui.sync.do.surname": "written as decided in the app",
|
|
1358
1358
|
"ui.sync.do.surname.kept": "stays as the research has it",
|
|
1359
1359
|
"ui.sync.do.surname.report": "nothing changes",
|
|
1360
|
+
"ui.sync.lineage.founder": "{n}. Lineage: {name} begins a lineage of their own",
|
|
1361
|
+
"ui.sync.lineage.nocarry": "{n}. Lineage: {name} carries no lineage",
|
|
1362
|
+
"ui.sync.lineage.none": "{n}. Lineage: {name} as computed again",
|
|
1363
|
+
"ui.sync.do.lineage": "written as set in the app",
|
|
1360
1364
|
"ui.sync.do.part": "added to the fact, citing the tree sent",
|
|
1361
1365
|
"ui.sync.do.part.user": "the edit wins (the research's in its history)",
|
|
1362
1366
|
"ui.sync.do.person.cite": "their name cites it (a lead until the research reads the record)",
|
|
@@ -1725,6 +1729,10 @@ export const UI = {
|
|
|
1725
1729
|
"ui.check.streetplaces": "{n, plural, one {# fact holds} other {# facts hold}} a street or a house number in the place",
|
|
1726
1730
|
"ui.error.tree.not-downloaded": "{n, plural, one {# file} other {# files}} of the research not downloaded from iCloud yet (the first: {first}) — nothing written",
|
|
1727
1731
|
"ui.error.tree.not-downloaded.hint": "open the research's folder in Finder and download them (or take the folder out of iCloud Drive), then run it again — not strom repair",
|
|
1732
|
+
"ui.error.tree.changed-outside": "the research's data was changed outside strom ({n, plural, one {# problem} other {# problems}}): nothing written",
|
|
1733
|
+
"ui.error.tree.changed-outside.hint": "strom verify (details)\nstrom repair (restores the last sealed state)",
|
|
1734
|
+
"ui.error.tree.changed-outside.keys.hint": "strom verify (details)\nstrom repair, then strom seal adopt (undoes only what was done outside strom — the work of both keys of this computer stays)",
|
|
1735
|
+
"ui.error.tree.changed-outside.terminal.hint": "strom verify (details)\nin a terminal of its own: strom repair, then strom seal adopt — it sees both keys and undoes only what was done outside strom",
|
|
1728
1736
|
"ui.export.file": "{file} ({for}) — {persons, plural, one {# person} other {# persons}} · {families, plural, one {# family} other {# families}} · {facts, plural, one {# fact} other {# facts}} · {sources, plural, one {# source} other {# sources}}{skipped}",
|
|
1729
1737
|
"ui.export.for.strom": "for the Strom app",
|
|
1730
1738
|
"ui.export.for.strom.images": "for the Strom app, with images",
|
package/dist/commands/output.js
CHANGED
|
@@ -13,8 +13,9 @@ import { UsageError } from "../core/errors.js";
|
|
|
13
13
|
import { excerptSettings, planExcerpts } from "../core/excerpt.js";
|
|
14
14
|
import { runGit, tracked } from "../core/git.js";
|
|
15
15
|
import { Settings } from "../core/config.js";
|
|
16
|
-
import { appKnowsArchive, appKnowsNoCouple, appShowsHypothesisLinks, appShowsReadings, appShowsOmittedImages, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsSourceReads, appShowsFactStatus, appShowsStoryDrafts, appTurnsExcerpts, appReadsSurnameGroups } from "../core/stromapp.js";
|
|
16
|
+
import { appKnowsArchive, appKnowsNoCouple, appShowsHypothesisLinks, appShowsReadings, appShowsOmittedImages, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsSourceReads, appShowsFactStatus, appShowsStoryDrafts, appTurnsExcerpts, appReadsSurnameGroups, appReadsLines } from "../core/stromapp.js";
|
|
17
17
|
import { surnamesForExport } from "../core/surnames.js";
|
|
18
|
+
import { lineMarks } from "../core/lineages.js";
|
|
18
19
|
import { EXCERPT_QUALITIES, EXCERPT_SCOPES } from "../core/model.js";
|
|
19
20
|
import { ui } from "../cli/ui.js";
|
|
20
21
|
import { isAgent } from "../core/which.js";
|
|
@@ -98,7 +99,9 @@ export function writeTreeGed(tree, opts) {
|
|
|
98
99
|
const omitted = !!images && appShowsOmittedImages(new Settings(tree.env, {}), opts.stromVersion);
|
|
99
100
|
// …and the groups of surname forms the user made one family and the suggestions rejected, for an app that reads them
|
|
100
101
|
const surnameGroups = opts.for === "strom" && appReadsSurnameGroups(new Settings(tree.env, {}), opts.stromVersion) ? surnamesForExport(tree) : undefined;
|
|
101
|
-
|
|
102
|
+
// …and the bounds of a lineage the person set, for an app that keeps them
|
|
103
|
+
const lines = opts.for === "strom" && appReadsLines(new Settings(tree.env, {}), opts.stromVersion) ? lineMarks(tree) : undefined;
|
|
104
|
+
const result = exportGedcom(tree, { for: opts.for, ...(surnameGroups ? { surnameGroups } : {}), ...(lines ? { lines } : {}), stromVersion: opts.stromVersion, ...(persons ? { persons } : {}), ...(images ? { excerpts: images.of } : {}), ...(clips ? { clips } : {}), ...(research ? { research } : {}), ...(edges ? { edges } : {}), ...(storyDrafts ? { storyDrafts } : {}), ...(coupleResi ? { coupleResi } : {}), ...(sourceReads ? { sourceReads } : {}), ...(factStatus ? { factStatus } : {}), ...(archive ? { archive } : {}), ...(turnsExcerpts ? { turnsExcerpts } : {}), ...(noCouple ? { noCouple } : {}), ...(titles ? { titles } : {}), ...(hypothesisLinks ? { hypothesisLinks } : {}), ...(conflictSides ? { conflictSides } : {}), ...(readings ? { readings } : {}), ...(omitted ? { omitted: images.omitted } : {}) });
|
|
102
105
|
const findings = validateGedcom(result.text, opts.for === "standard" ? { strict: true } : {});
|
|
103
106
|
const file = opts.out ?? gedFile(tree, opts.for, opts.research);
|
|
104
107
|
if (!hasGedErrors(findings) && !tree.dryRun) {
|
package/dist/commands/sync.js
CHANGED
|
@@ -94,6 +94,12 @@ function changeLine(tree, c, incoming, lang, readings) {
|
|
|
94
94
|
const p = tree.get(key);
|
|
95
95
|
return p ? `${(p.names[0] ? `${p.names[0].given} ${p.names[0].surname}` : key).trim()} [${key}]` : key;
|
|
96
96
|
};
|
|
97
|
+
// a person's bound of a lineage the user set in the app (core/lineages.ts)
|
|
98
|
+
if (c.line) {
|
|
99
|
+
const key = c.line.mark === "FOUNDER" ? "ui.sync.lineage.founder" : c.line.mark === "NOCARRY" ? "ui.sync.lineage.nocarry" : "ui.sync.lineage.none";
|
|
100
|
+
const does = c.takenBack ? "ui.sync.do.taken" : c.action === "report" ? "ui.sync.do.surname.report" : "ui.sync.do.lineage";
|
|
101
|
+
return `${ui(lang, key, { n: c.n, name: name(c.person) })} → ${ui(lang, does)}`;
|
|
102
|
+
}
|
|
97
103
|
const who = c.partners ? c.partners.map(name).join(" & ") || c.family || "?" : name(c.person);
|
|
98
104
|
// a partner in their age: the given name
|
|
99
105
|
const short = (key) => (key.startsWith("x:") ? name(key) : (tree.get(key)?.names[0]?.given ?? key));
|
package/dist/core/changelog.js
CHANGED
|
@@ -432,6 +432,19 @@ export function changeLines(tree, ops, subject, lang, was, tasks) {
|
|
|
432
432
|
say(kind === "undo" ? phrase(lang, "log.surname.undo") : kind in { group: 1, ungroup: 1, reject: 1, unreject: 1 } ? phrase(lang, k, { forms: short(forms, 120) }) : changed(o));
|
|
433
433
|
continue;
|
|
434
434
|
}
|
|
435
|
+
// the bound of a lineage the person set in the Strom app (quality/lineages.json): the person by name, never the summary
|
|
436
|
+
if (o.op === "lineage.set" || o.op === "lineage.undo") {
|
|
437
|
+
const m = /^(\S+) (begins a lineage|carries no lineage|lineage as computed)/u.exec(o.summary);
|
|
438
|
+
const person = who(o.targets[0] ?? m?.[1]);
|
|
439
|
+
say(o.op === "lineage.undo"
|
|
440
|
+
? phrase(lang, "log.lineage.undo", { who: person })
|
|
441
|
+
: m?.[2] === "begins a lineage"
|
|
442
|
+
? phrase(lang, "log.lineage.founder", { who: person })
|
|
443
|
+
: m?.[2] === "carries no lineage"
|
|
444
|
+
? phrase(lang, "log.lineage.nocarry", { who: person })
|
|
445
|
+
: phrase(lang, "log.lineage.none", { who: person }));
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
435
448
|
// the names of participants set apart from their notes by strom's fix, or put back (quality/participant-names.json)
|
|
436
449
|
if (o.op === "participant.fix" || o.op === "participant.unfix") {
|
|
437
450
|
const n = /: (\d+) in /u.exec(o.summary)?.[1] ?? String(o.targets.length);
|
package/dist/core/import.js
CHANGED
|
@@ -672,17 +672,26 @@ function importLabel(lang) {
|
|
|
672
672
|
export function isStromJson(data) {
|
|
673
673
|
return !!data && typeof data === "object" && "persons" in data && "partnerships" in data;
|
|
674
674
|
}
|
|
675
|
-
/** Strom flexible date ("~1900", "<1900-05", "1900-05-03") → GEDCOM. */
|
|
675
|
+
/** Strom flexible date ("~1900", "<1900-05", "1900-05-03", a range "1785..1804", a period "1871/1882") → GEDCOM. */
|
|
676
676
|
export function fromFlexDate(flex) {
|
|
677
677
|
if (!flex)
|
|
678
678
|
return undefined;
|
|
679
|
+
// a period ("1871/1882", "1871/", "/1882"): from the one to the other, each side a plain date
|
|
680
|
+
const period = /^([^/]*)\/([^/]*)$/.exec(flex.trim());
|
|
681
|
+
if (period) {
|
|
682
|
+
const plain = (s) => (/^\d{3,4}(?:-\d{1,2}(?:-\d{1,2})?)?$/.test(s) ? fromFlexDate(s) : undefined);
|
|
683
|
+
const [a, b] = [period[1] ? plain(period[1]) : "", period[2] ? plain(period[2]) : ""];
|
|
684
|
+
if (a === undefined || b === undefined || (!a && !b))
|
|
685
|
+
return undefined;
|
|
686
|
+
return normalizeDate(a && b ? `FROM ${a} TO ${b}` : a ? `FROM ${a}` : `TO ${b}`);
|
|
687
|
+
}
|
|
679
688
|
// a range ("1785..1804"): between the two
|
|
680
689
|
const range = /^(.+?)\.\.(.+)$/.exec(flex.trim());
|
|
681
690
|
if (range) {
|
|
682
691
|
const [a, b] = [fromFlexDate(range[1]), fromFlexDate(range[2])];
|
|
683
692
|
return a && b && !/^(ABT|BEF|AFT|BET|FROM|TO) /.test(a) && !/^(ABT|BEF|AFT|BET|FROM|TO) /.test(b) ? normalizeDate(`BET ${a} AND ${b}`) : undefined;
|
|
684
693
|
}
|
|
685
|
-
const m = /^([~<>])?(\d{3,4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(flex.trim());
|
|
694
|
+
const m = /^([~<>])?(\d{3,4})(?:-(\d{1,2}))?(?:-(\d{1,2}))?$/.exec(flex.trim());
|
|
686
695
|
if (!m)
|
|
687
696
|
return importDate(flex);
|
|
688
697
|
const months = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
|
package/dist/core/integrity.js
CHANGED
|
@@ -518,9 +518,14 @@ export function assertIntact(tree) {
|
|
|
518
518
|
: tree.key && errors.some((f) => f.code === "foreign-commit") && lastSealed(tree, tree.key).inTerminal
|
|
519
519
|
? REPAIR_IN_TERMINAL_HINT
|
|
520
520
|
: undefined;
|
|
521
|
+
// a person reads it in the research's language (ui.error.tree.changed-outside), the way said as one of three hints
|
|
522
|
+
const inTerminal = way !== undefined && (way === REPAIR_IN_TERMINAL_HINT || way.startsWith("the user runs in their terminal"));
|
|
521
523
|
throw new StromError(`data was changed outside strom (${counted(errors.length, "problem")}) — refusing to write`, {
|
|
522
524
|
hint: `strom verify (details)\n${way ?? "strom repair (restore the last sealed state)"}`,
|
|
523
525
|
details: errors,
|
|
526
|
+
code: "tree.changed-outside",
|
|
527
|
+
params: { n: String(errors.length) },
|
|
528
|
+
...(way ? { hintCode: inTerminal ? "tree.changed-outside.terminal" : "tree.changed-outside.keys" } : {}),
|
|
524
529
|
});
|
|
525
530
|
}
|
|
526
531
|
/**
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// The bounds of a lineage the person set in the Strom app (spec 028; the app's ZADANI_VYZKUM_rody.md): the app computes a
|
|
2
|
+
// lineage (the male line from the oldest bearer of the name) and the person may correct it in two ways — a person who
|
|
3
|
+
// begins a lineage of their own (a founder: their tie to their father does not count for it), a person who carries
|
|
4
|
+
// none (their sons begin lineages). <tree>/quality/lineages.json keeps both lists of people (P…), written whole as a
|
|
5
|
+
// sealed operation of the tree like quality/surnames.json (core/integrity.ts): a change outside strom is an error of
|
|
6
|
+
// strom guard; strom sync undo and the history take it back.
|
|
7
|
+
// Only the person sets them, in the Strom app: strom sync takes them from the app's file (1 _STROM_LINE on a person,
|
|
8
|
+
// 1 _STROM_LINES Y in the header), the Strom file gives them back (gate APP_READS_LINES). No evidence and no fact of a
|
|
9
|
+
// person: they change no name, no parents, no status — a locked person may have one.
|
|
10
|
+
// A person merged since is read as the one merged into (a founder before one who carries none); one taken back has none.
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { readJsonIfExists, stringifyCanonical } from "./json.js";
|
|
13
|
+
import { personNow } from "./quality.js";
|
|
14
|
+
import * as git from "./git.js";
|
|
15
|
+
export const LINEAGES_FILE = "quality/lineages.json";
|
|
16
|
+
const PERSON_ID = /^P\d{1,9}$/u;
|
|
17
|
+
/** A person's ID as the research writes it (P12 → P0012); undefined when it is none. */
|
|
18
|
+
function idOf(raw) {
|
|
19
|
+
if (typeof raw !== "string")
|
|
20
|
+
return undefined;
|
|
21
|
+
const id = raw.trim().toUpperCase();
|
|
22
|
+
if (!PERSON_ID.test(id))
|
|
23
|
+
return undefined;
|
|
24
|
+
return id.length < 5 ? `P${id.slice(1).padStart(4, "0")}` : id;
|
|
25
|
+
}
|
|
26
|
+
/** A mark as the Strom file writes it; anything else (a value of a later app) is none. */
|
|
27
|
+
export function lineMarkOf(raw) {
|
|
28
|
+
const v = raw?.trim().toUpperCase();
|
|
29
|
+
return v === "FOUNDER" || v === "NOCARRY" ? v : undefined;
|
|
30
|
+
}
|
|
31
|
+
const sorted = (ids) => [...new Set(ids)].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
32
|
+
function listOf(raw) {
|
|
33
|
+
return Array.isArray(raw) ? sorted(raw.map(idOf).filter((x) => !!x)) : [];
|
|
34
|
+
}
|
|
35
|
+
/** The lists as a file (or a commit's copy of it) holds them; empty when there is none. */
|
|
36
|
+
export function lineagesOf(text) {
|
|
37
|
+
if (!text)
|
|
38
|
+
return { lineFounders: [], lineNotCarriers: [] };
|
|
39
|
+
try {
|
|
40
|
+
const f = JSON.parse(text);
|
|
41
|
+
return { lineFounders: listOf(f?.lineFounders), lineNotCarriers: listOf(f?.lineNotCarriers) };
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return { lineFounders: [], lineNotCarriers: [] };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function readLineages(tree) {
|
|
48
|
+
try {
|
|
49
|
+
const f = readJsonIfExists(path.join(tree.root, ...LINEAGES_FILE.split("/")));
|
|
50
|
+
return { lineFounders: listOf(f?.lineFounders), lineNotCarriers: listOf(f?.lineNotCarriers) };
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return { lineFounders: [], lineNotCarriers: [] };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The lists at a commit (what the export at it gave the Strom app); undefined when the history has no such commit. */
|
|
57
|
+
export function lineagesAt(tree, head) {
|
|
58
|
+
if (!/^[0-9a-f]{7,40}$/u.test(head))
|
|
59
|
+
return undefined;
|
|
60
|
+
if (git.runGit(tree.root, ["cat-file", "-e", `${head}^{commit}`]).status !== 0)
|
|
61
|
+
return undefined;
|
|
62
|
+
return lineagesOf(git.showFile(tree.root, head, LINEAGES_FILE));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Each person's mark as the research reads it now: by who they are now (merged: the one merged into), a person taken
|
|
66
|
+
* back (or not in the research) none, a founder before one who carries none.
|
|
67
|
+
*/
|
|
68
|
+
export function lineMarks(tree, file = readLineages(tree)) {
|
|
69
|
+
const out = new Map();
|
|
70
|
+
const live = (id) => {
|
|
71
|
+
const now = personNow(tree, id);
|
|
72
|
+
const p = tree.get(now);
|
|
73
|
+
return p && p.type === "person" && !p.retracted ? now : undefined;
|
|
74
|
+
};
|
|
75
|
+
for (const id of file.lineFounders) {
|
|
76
|
+
const p = live(id);
|
|
77
|
+
if (p)
|
|
78
|
+
out.set(p, "FOUNDER");
|
|
79
|
+
}
|
|
80
|
+
for (const id of file.lineNotCarriers) {
|
|
81
|
+
const p = live(id);
|
|
82
|
+
if (p && !out.has(p))
|
|
83
|
+
out.set(p, "NOCARRY");
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/** Written whole as a sealed operation of the tree, inside its lock. */
|
|
88
|
+
function writeLineages(tree, next, op) {
|
|
89
|
+
const founders = sorted(next.lineFounders);
|
|
90
|
+
const notCarriers = sorted(next.lineNotCarriers).filter((id) => !founders.includes(id));
|
|
91
|
+
tree.withTreeLock(() => tree.putFile(LINEAGES_FILE, stringifyCanonical({ lineFounders: founders, lineNotCarriers: notCarriers }), { ...op, summary: op.summary.normalize("NFC") }));
|
|
92
|
+
}
|
|
93
|
+
/** A mark as the history says it. */
|
|
94
|
+
export const markSaid = (m) => (m === "FOUNDER" ? "begins a lineage" : m === "NOCARRY" ? "carries no lineage" : "lineage as computed");
|
|
95
|
+
/**
|
|
96
|
+
* One person's mark set (null: none — the lineage as the app computes it): every ID of the file that is this person now
|
|
97
|
+
* (one merged into them) taken out of both lists, the person put in the one of the mark. Undefined when nothing changes;
|
|
98
|
+
* else the mark before, for an undo.
|
|
99
|
+
*/
|
|
100
|
+
export function setLineMark(tree, person, mark, why, op = "lineage.set") {
|
|
101
|
+
const file = readLineages(tree);
|
|
102
|
+
const was = lineMarks(tree, file).get(person) ?? null;
|
|
103
|
+
const theirs = (id) => id === person || personNow(tree, id) === person;
|
|
104
|
+
const founders = file.lineFounders.filter((id) => !theirs(id));
|
|
105
|
+
const notCarriers = file.lineNotCarriers.filter((id) => !theirs(id));
|
|
106
|
+
if (mark === "FOUNDER")
|
|
107
|
+
founders.push(person);
|
|
108
|
+
if (mark === "NOCARRY")
|
|
109
|
+
notCarriers.push(person);
|
|
110
|
+
const next = { lineFounders: sorted(founders), lineNotCarriers: sorted(notCarriers) };
|
|
111
|
+
if (JSON.stringify(next) === JSON.stringify({ lineFounders: file.lineFounders, lineNotCarriers: file.lineNotCarriers }))
|
|
112
|
+
return undefined;
|
|
113
|
+
writeLineages(tree, next, { op, targets: [person], summary: `${person} ${markSaid(mark)} (${why})` });
|
|
114
|
+
return { was };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* What the Strom app's marks change in the research. `theirs`: the file's marks by the research's ID (or the file's key
|
|
118
|
+
* of a new person), undefined where the file says nothing of them (no header mark: an older app); `present`: the people
|
|
119
|
+
* the file has (a mark is taken away only from one of them); `base`: the marks the research gave the app at
|
|
120
|
+
* _STROM_HEAD, when it gave them — only what the user changed since counts (a mark they kept as given leaves the
|
|
121
|
+
* research's word); without one the file's list is the truth, an empty one too.
|
|
122
|
+
*/
|
|
123
|
+
export function planLines(now, theirs, present, base) {
|
|
124
|
+
if (!theirs)
|
|
125
|
+
return [];
|
|
126
|
+
const out = [];
|
|
127
|
+
const people = sorted([...theirs.keys(), ...now.keys(), ...(base?.keys() ?? [])]);
|
|
128
|
+
for (const id of people) {
|
|
129
|
+
const t = theirs.get(id) ?? null;
|
|
130
|
+
const n = now.get(id) ?? null;
|
|
131
|
+
if (t === n)
|
|
132
|
+
continue;
|
|
133
|
+
// taken away only from someone the file has (one it lacks says nothing of them)
|
|
134
|
+
if (!t && !present(id))
|
|
135
|
+
continue;
|
|
136
|
+
if (base && t === (base.get(id) ?? null))
|
|
137
|
+
continue;
|
|
138
|
+
out.push({ person: id, mark: t, was: n });
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/** A change of a sync taken back: the person's mark before it again — unless it changed since (the newer word stands). */
|
|
143
|
+
export function undoLineMark(tree, person, mark, was, why) {
|
|
144
|
+
if ((lineMarks(tree).get(person) ?? null) !== mark)
|
|
145
|
+
return false;
|
|
146
|
+
return !!setLineMark(tree, person, was, why, "lineage.undo");
|
|
147
|
+
}
|
package/dist/core/live.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
//
|
|
10
10
|
// GET <token>/status the tree, its researches, who is at work, what waits; what the research takes from
|
|
11
11
|
// the app (accepts). ?poll=1: the app asking now and then — the bridge's idle time goes on
|
|
12
|
-
// GET <token>/tree.ged the tree for the Strom app, as it is now, each entry with its image (the settings excerpts.*)
|
|
12
|
+
// GET <token>/tree.ged the tree for the Strom app, as it is now, each entry with its image (the settings excerpts.*);
|
|
13
|
+
// for an app that keeps them the bounds of a lineage the person set (_STROM_LINE, _STROM_LINES;
|
|
14
|
+
// feature lineage.read: strom sync takes them back)
|
|
13
15
|
// GET <token>/images.ged the same (the address an older strom gave)
|
|
14
16
|
// GET <token>/log what the research saved, newest first (each commit: when, what, for which task;
|
|
15
17
|
// text: its lines as the user reads them, kinds: what each is about, sources: the S… each
|
|
@@ -215,8 +217,9 @@ import { knownNewerVersion, updateChannel } from "./update.js";
|
|
|
215
217
|
import { whatsNew } from "./whatsnew.js";
|
|
216
218
|
import { gitProgram, runGit } from "./git.js";
|
|
217
219
|
import { RECENT_HOURS, recentChanges, recentNow } from "./recent.js";
|
|
218
|
-
import { appKnowsArchive, appKnowsNoCouple, appShowsHypothesisLinks, appShowsOmittedImages, appShowsReadings, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsFactStatus, appShowsSourceReads, appShowsStoryDrafts, appTurnsExcerpts, appReadsSurnameGroups, appUrlSetting, isAppVersion, isStromAppOrigin, stromAppState } from "./stromapp.js";
|
|
220
|
+
import { appKnowsArchive, appKnowsNoCouple, appShowsHypothesisLinks, appShowsOmittedImages, appShowsReadings, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsFactStatus, appShowsSourceReads, appShowsStoryDrafts, appTurnsExcerpts, appReadsSurnameGroups, appReadsLines, appUrlSetting, isAppVersion, isStromAppOrigin, stromAppState } from "./stromapp.js";
|
|
219
221
|
import { suggestSurnames, surnamesForExport } from "./surnames.js";
|
|
222
|
+
import { lineMarks } from "./lineages.js";
|
|
220
223
|
import { judgePairs, pairsOf, PAIRS_MAX } from "./duplicates.js";
|
|
221
224
|
import { askOf, qualityRev, readDismissed } from "./quality.js";
|
|
222
225
|
import { Settings } from "./config.js";
|
|
@@ -925,7 +928,7 @@ function idsOf(applied, known) {
|
|
|
925
928
|
return Object.keys(ids.persons).length || Object.keys(ids.sources).length ? { ids } : {};
|
|
926
929
|
}
|
|
927
930
|
/** What the bridge does that an app may ask about (each added once, never taken away). */
|
|
928
|
-
export const BRIDGE_FEATURES = ["sync.again", "sync.undoneSince", "sync.takenBack", "sync.conflictEdit", "sync.since", "sync.ids", "family.noCouple", "family.alone", "adopt.transfer", "adopt.empty", "material.list", "person.titles", "media.codes", "hypothesis.links", "status.recent", "spend.readers", "hypothesis.intree", "archive.permission", "task.check", "waiting.groups", "waiting.sent", "waiting.note", "batch.kept", "live.end", "whats.new", "terminal.choice", "person.marriedSurname", "phone", "read.now", "read.sync", "excerpt.get", "log.sources", "material.assign", "material.thumb", "seal.foreign", "tree.state", "disk.full", "tree.notDownloaded", "sync.removed", "tree.kind", "experiments", "experiments.off", "experiments.link", "experiments.split", "history.events", "history.hide", "experiments.pace", "history.first", "history.regions", "history.hide.all", "history.skip", "history.map", "history.delete", "history.delete.restore", "stories.history", "history.place", "surnames.suggest", "surnames.read", "duplicates.judge", "quality.dismiss", "quality.read", "person.primary", "fixes.list"];
|
|
931
|
+
export const BRIDGE_FEATURES = ["sync.again", "sync.undoneSince", "sync.takenBack", "sync.conflictEdit", "sync.since", "sync.ids", "family.noCouple", "family.alone", "adopt.transfer", "adopt.empty", "material.list", "person.titles", "media.codes", "hypothesis.links", "status.recent", "spend.readers", "hypothesis.intree", "archive.permission", "task.check", "waiting.groups", "waiting.sent", "waiting.note", "batch.kept", "live.end", "whats.new", "terminal.choice", "person.marriedSurname", "phone", "read.now", "read.sync", "excerpt.get", "log.sources", "material.assign", "material.thumb", "seal.foreign", "tree.state", "disk.full", "tree.notDownloaded", "sync.removed", "tree.kind", "experiments", "experiments.off", "experiments.link", "experiments.split", "history.events", "history.hide", "experiments.pace", "history.first", "history.regions", "history.hide.all", "history.skip", "history.map", "history.delete", "history.delete.restore", "stories.history", "history.place", "surnames.suggest", "surnames.read", "lineage.read", "duplicates.judge", "quality.dismiss", "quality.read", "person.primary", "fixes.list"];
|
|
929
932
|
/** /status recent, when it is ready. */
|
|
930
933
|
function recentOf(root, env, head) {
|
|
931
934
|
const recent = head ? recentNow(root, env, head) : undefined;
|
|
@@ -1664,6 +1667,8 @@ export function serveLive(root, env) {
|
|
|
1664
1667
|
const omitted = !!images && appShowsOmittedImages(new Settings(env, {}), version);
|
|
1665
1668
|
// …and the groups of surname forms and the suggestions rejected, for an app that reads them (strom sync takes them back)
|
|
1666
1669
|
const surnameGroups = appReadsSurnameGroups(new Settings(env, {}), version) ? surnamesForExport(tree) : undefined;
|
|
1670
|
+
// …and the bounds of a lineage the person set, for an app that keeps them (strom sync takes them back)
|
|
1671
|
+
const lines = appReadsLines(new Settings(env, {}), version) ? lineMarks(tree) : undefined;
|
|
1667
1672
|
left = {
|
|
1668
1673
|
head: h,
|
|
1669
1674
|
level: set?.quality ?? LEVELS[1].name,
|
|
@@ -1674,7 +1679,7 @@ export function serveLive(root, env) {
|
|
|
1674
1679
|
head: h,
|
|
1675
1680
|
links: offered.join(" "),
|
|
1676
1681
|
version: version ?? "",
|
|
1677
|
-
text: exportGedcom(tree, { for: "strom", ...(h ? { head: h } : {}), ...(surnameGroups ? { surnameGroups } : {}), ...(images ? { excerpts: images.of } : {}), ...(opens ? { clips: true, research: true } : {}), ...(edges ? { edges } : {}), ...(storyDrafts ? { storyDrafts } : {}), ...(coupleResi ? { coupleResi } : {}), ...(sourceReads ? { sourceReads } : {}), ...(factStatus ? { factStatus } : {}), ...(archive ? { archive } : {}), ...(turnsExcerpts ? { turnsExcerpts } : {}), ...(noCouple ? { noCouple } : {}), ...(titles ? { titles } : {}), ...(hypothesisLinks ? { hypothesisLinks } : {}), ...(conflictSides ? { conflictSides } : {}), ...(readings ? { readings } : {}), ...(omitted ? { omitted: images.omitted } : {}), ...(offered.length ? { links: offered, ...(linkScheme(env) !== LINK_SCHEME ? { linkScheme: linkScheme(env) } : {}) } : {}) }).text,
|
|
1682
|
+
text: exportGedcom(tree, { for: "strom", ...(h ? { head: h } : {}), ...(surnameGroups ? { surnameGroups } : {}), ...(lines ? { lines } : {}), ...(images ? { excerpts: images.of } : {}), ...(opens ? { clips: true, research: true } : {}), ...(edges ? { edges } : {}), ...(storyDrafts ? { storyDrafts } : {}), ...(coupleResi ? { coupleResi } : {}), ...(sourceReads ? { sourceReads } : {}), ...(factStatus ? { factStatus } : {}), ...(archive ? { archive } : {}), ...(turnsExcerpts ? { turnsExcerpts } : {}), ...(noCouple ? { noCouple } : {}), ...(titles ? { titles } : {}), ...(hypothesisLinks ? { hypothesisLinks } : {}), ...(conflictSides ? { conflictSides } : {}), ...(readings ? { readings } : {}), ...(omitted ? { omitted: images.omitted } : {}), ...(offered.length ? { links: offered, ...(linkScheme(env) !== LINK_SCHEME ? { linkScheme: linkScheme(env) } : {}) } : {}) }).text,
|
|
1678
1683
|
};
|
|
1679
1684
|
}
|
|
1680
1685
|
return h;
|
|
@@ -3903,7 +3908,8 @@ export function serveLive(root, env) {
|
|
|
3903
3908
|
const tree = Tree.open(root, env);
|
|
3904
3909
|
const settings = new Settings(env, {});
|
|
3905
3910
|
const rev = historyShown(tree, settings) ? historyRevNow(tree, settings, h, historyRev(tree, settings.shared()?.value, settings)) : undefined;
|
|
3906
|
-
|
|
3911
|
+
// (a commit made since the head was read changes the rev too: its change says it, never a history of its own)
|
|
3912
|
+
if (rev && historySeen && historySeen.head === h && historySeen.rev !== rev && tip(root).head === h)
|
|
3907
3913
|
send("history", { rev });
|
|
3908
3914
|
historySeen = rev ? { rev, head: h } : undefined;
|
|
3909
3915
|
}
|
package/dist/core/phrases.js
CHANGED
|
@@ -318,6 +318,10 @@ export const PHRASES = {
|
|
|
318
318
|
"log.surname.reject": "Surname forms of no one family: {forms}",
|
|
319
319
|
"log.surname.unreject": "Surname forms may be suggested again: {forms}",
|
|
320
320
|
"log.surname.undo": "Surname forms put back as they were",
|
|
321
|
+
"log.lineage.founder": "Begins a lineage of their own: {who}",
|
|
322
|
+
"log.lineage.nocarry": "Carries no lineage: {who}",
|
|
323
|
+
"log.lineage.none": "Lineage as computed again: {who}",
|
|
324
|
+
"log.lineage.undo": "Lineage put back as it was: {who}",
|
|
321
325
|
"log.participant.fix": "Names of godparents and witnesses set apart from their notes: {n}",
|
|
322
326
|
"log.participant.unfix": "Names of godparents and witnesses put back as they were: {n}",
|
|
323
327
|
"fix.appshape.resolution": "The research's value stays: {value}",
|
package/dist/core/stromapp.js
CHANGED
|
@@ -558,6 +558,26 @@ export function appKnowsPrimaryName(settings, version) {
|
|
|
558
558
|
return compareVersions(v, APP_PRIMARY_NAME_BETA) >= 0;
|
|
559
559
|
return !!APP_KNOWS_PRIMARY_NAME && compareVersions(v, APP_KNOWS_PRIMARY_NAME) >= 0;
|
|
560
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* The first Strom app that keeps the person's own bounds of a lineage (spec 028; the app's ZADANI_VYZKUM_rody.md):
|
|
563
|
+
* it reads 1 _STROM_LINE FOUNDER|NOCARRY on a person and 1 _STROM_LINES Y in the header of the Strom file, keeps them
|
|
564
|
+
* as its lineageOverrides and sends them back. An older app skips the tags and sends none back (a send of it says
|
|
565
|
+
* nothing of them: no header mark). Not released yet (the app's 3.19.0); an app of unknown version (strom app from a
|
|
566
|
+
* terminal) stays off, as the groups of a surname's forms; another copy of the app (its beta, its development) gets
|
|
567
|
+
* them from APP_READS_LINES_BETA.
|
|
568
|
+
*/
|
|
569
|
+
export const APP_READS_LINES = undefined;
|
|
570
|
+
/** The first beta of the app that reads and writes the bounds of a lineage (another copy of the app gets them from it). */
|
|
571
|
+
export const APP_READS_LINES_BETA = "3.19.0-beta.4";
|
|
572
|
+
/** Does the Strom app of this version keep the bounds of a lineage the person set (_STROM_LINE, _STROM_LINES)? */
|
|
573
|
+
export function appReadsLines(settings, version, gate = APP_READS_LINES) {
|
|
574
|
+
const v = version?.trim();
|
|
575
|
+
if (!v || !isAppVersion(v))
|
|
576
|
+
return false;
|
|
577
|
+
if (anotherCopy(settings) && APP_READS_LINES_BETA)
|
|
578
|
+
return compareVersions(v, APP_READS_LINES_BETA) >= 0;
|
|
579
|
+
return !!gate && compareVersions(v, gate) >= 0;
|
|
580
|
+
}
|
|
561
581
|
/** The address that opens the Strom app to send the user's edited tree to the bridge. */
|
|
562
582
|
export function sendAppUrl(bridge, settings) {
|
|
563
583
|
return `${stromAppUrl(settings)}?send=${encodeURIComponent(bridge)}`;
|
package/dist/core/sync.js
CHANGED
|
@@ -42,7 +42,8 @@ import * as git from "./git.js";
|
|
|
42
42
|
import { now, Tree, typeOfId } from "./tree.js";
|
|
43
43
|
import { actsAsArchive } from "./state.js";
|
|
44
44
|
import { appSides } from "./conflicts.js";
|
|
45
|
-
import { APP_DATA_SEX_UNKNOWN, appKeepsPeriods, appKnowsPrimaryName, appReadsSurnameGroups, isAppVersion } from "./stromapp.js";
|
|
45
|
+
import { APP_DATA_SEX_UNKNOWN, appKeepsPeriods, appKnowsPrimaryName, appReadsLines, appReadsSurnameGroups, isAppVersion } from "./stromapp.js";
|
|
46
|
+
import { lineMarkOf, lineMarks, lineagesAt, planLines, setLineMark, undoLineMark } from "./lineages.js";
|
|
46
47
|
import { Settings } from "./config.js";
|
|
47
48
|
import { applySurnameChange, formsSaid, planSurnames, readMarkLines, readSurnames, surnamesAt, undoSurnameChange } from "./surnames.js";
|
|
48
49
|
import { SURNAME_GROUPS_MARK, SURNAME_REJECTIONS_MARK } from "../gedcom/export.js";
|
|
@@ -585,6 +586,8 @@ export function readGedcom(text) {
|
|
|
585
586
|
}
|
|
586
587
|
// the sources of the person: the Strom app's (1 SOUR), the research's of their names
|
|
587
588
|
const cites = [...children(r, "SOUR"), ...children(r, "NAME").flatMap((n) => children(n, "SOUR"))].map(cite).filter((x) => !!x);
|
|
589
|
+
// their bound of a lineage (a value of a later app: none)
|
|
590
|
+
const line = children(r, "_STROM_LINE").map((n) => lineMarkOf(n.value)).reduce((a, m) => (a === "FOUNDER" || m === "FOUNDER" ? "FOUNDER" : (a ?? m)), undefined);
|
|
588
591
|
persons.set(key, {
|
|
589
592
|
key,
|
|
590
593
|
names,
|
|
@@ -596,6 +599,7 @@ export function readGedcom(text) {
|
|
|
596
599
|
...(titles ? { titles } : {}),
|
|
597
600
|
...(married.length ? { married } : {}),
|
|
598
601
|
...(primary ? { primary } : {}),
|
|
602
|
+
...(line ? { line } : {}),
|
|
599
603
|
});
|
|
600
604
|
}
|
|
601
605
|
// how a child belongs to a family (the child's FAMC + PEDI: adopted, foster, step): none said is birth
|
|
@@ -664,10 +668,12 @@ export function readGedcom(text) {
|
|
|
664
668
|
const again = val(head, "_STROM_AGAIN")?.trim();
|
|
665
669
|
const appVersion = fromApp.app ? val(children(head, "SOUR")[0], "VERS")?.trim() : undefined;
|
|
666
670
|
const surnames = head ? surnameMarksOf(head) : undefined;
|
|
671
|
+
const lines = /^y(es)?$/i.test(val(head, "_STROM_LINES")?.trim() ?? "");
|
|
667
672
|
return {
|
|
668
673
|
format: "gedcom",
|
|
669
674
|
...(appVersion ? { appVersion } : {}),
|
|
670
675
|
...(surnames ? { surnames } : {}),
|
|
676
|
+
...(lines ? { lines: true } : {}),
|
|
671
677
|
...(treeId ? { treeId } : {}),
|
|
672
678
|
...(at ? { head: at } : {}),
|
|
673
679
|
persons,
|
|
@@ -2623,6 +2629,25 @@ export function planSync(tree, incoming, edits, opts = {}) {
|
|
|
2623
2629
|
const action = !here || (given && samePoint(given, here)) || mirror ? "set" : "pick";
|
|
2624
2630
|
push({ kind: "place.coords", action, place: { ...p, ...(mine ? { id: mine.id } : {}), ...(here ? { was: here } : {}) } });
|
|
2625
2631
|
}
|
|
2632
|
+
// the bounds of a lineage the user set in the app: where the file says them (_STROM_LINES), against what the research
|
|
2633
|
+
// gave the app at _STROM_HEAD when it gave them (an app that keeps them: APP_READS_LINES) — else the file's are the truth
|
|
2634
|
+
if (incoming.lines) {
|
|
2635
|
+
const theirs = new Map();
|
|
2636
|
+
const present = new Set();
|
|
2637
|
+
for (const [key, p] of incoming.persons) {
|
|
2638
|
+
const id = key.startsWith("x:") ? key : resolve(tree, key)?.id;
|
|
2639
|
+
if (!id)
|
|
2640
|
+
continue;
|
|
2641
|
+
present.add(id);
|
|
2642
|
+
if (p.line && theirs.get(id) !== "FOUNDER")
|
|
2643
|
+
theirs.set(id, p.line);
|
|
2644
|
+
}
|
|
2645
|
+
const given = incoming.head && appReadsLines(new Settings(tree.env, {}), incoming.appVersion) ? lineagesAt(tree, incoming.head) : undefined;
|
|
2646
|
+
for (const c of planLines(lineMarks(tree), theirs, (id) => !partial && present.has(id), given ? lineMarks(tree, given) : undefined)) {
|
|
2647
|
+
const p = incoming.persons.get(c.person);
|
|
2648
|
+
push({ kind: "lineage.mark", action: "set", person: c.person, ...(p?.names[0] ? { name: p.names[0] } : {}), text: c.mark ?? "", line: c });
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2626
2651
|
if (incoming.head && opts.takenBack !== false && !sentAgain)
|
|
2627
2652
|
takenBack(tree, incoming, edits, changes);
|
|
2628
2653
|
// a send of the app (`sent`: it came through the bridge) with nothing to stand on (no _STROM_HEAD, no _STROM_SINCE the research still has —
|
|
@@ -4273,6 +4298,18 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
4273
4298
|
if (r)
|
|
4274
4299
|
applied.push({ do: "surname.set", id: r.list, before: JSON.stringify({ added: r.added ?? null, removed: r.removed }) });
|
|
4275
4300
|
}
|
|
4301
|
+
// the bounds of a lineage the user set in the app (quality/lineages.json): a new person's by the ID they got
|
|
4302
|
+
for (const c of plan.changes.filter((x) => x.line && taken(x))) {
|
|
4303
|
+
const id = who(c.line.person);
|
|
4304
|
+
const p = id ? resolve(tree, id) : undefined;
|
|
4305
|
+
if (!p) {
|
|
4306
|
+
skipped.push({ n: c.n, kind: c.kind, why: `${c.line.person} is not in the research`, code: "person.no-match", params: { person: c.line.person } });
|
|
4307
|
+
continue;
|
|
4308
|
+
}
|
|
4309
|
+
const r = setLineMark(tree, p.id, c.line.mark, reason);
|
|
4310
|
+
if (r)
|
|
4311
|
+
applied.push({ do: "lineage.set", id: p.id, before: JSON.stringify({ was: r.was, mark: c.line.mark }) });
|
|
4312
|
+
}
|
|
4276
4313
|
return { applied, changes: changes - skipped.length, skipped, ...(asked.length ? { asked } : {}) };
|
|
4277
4314
|
}
|
|
4278
4315
|
/** Where a task to check a reading of a document confirmed in the app comes from (one per person and its sources). */
|
|
@@ -4971,6 +5008,11 @@ export function undoSync(tree, input) {
|
|
|
4971
5008
|
undoSurnameChange(tree, a.id === "rejected" ? "rejected" : "groups", b.added ?? undefined, b.removed ?? [], reason);
|
|
4972
5009
|
break;
|
|
4973
5010
|
}
|
|
5011
|
+
case "lineage.set": {
|
|
5012
|
+
const b = JSON.parse(String(a.before ?? "{}"));
|
|
5013
|
+
undoLineMark(tree, a.id, b.mark ?? null, b.was ?? null, reason);
|
|
5014
|
+
break;
|
|
5015
|
+
}
|
|
4974
5016
|
}
|
|
4975
5017
|
n++;
|
|
4976
5018
|
}
|
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.24.0-beta.
|
|
32
|
+
export const VERSION = "1.24.0-beta.2";
|
|
33
33
|
const GITIGNORE = `# Strom Research — local state and large files are not versioned
|
|
34
34
|
.strom/
|
|
35
35
|
/media/
|
package/dist/gedcom/export.js
CHANGED
|
@@ -226,11 +226,16 @@ export function exportGedcom(tree, opts = {}) {
|
|
|
226
226
|
if (opts.linkScheme)
|
|
227
227
|
w.line(2, "_SCHEME", opts.linkScheme);
|
|
228
228
|
}
|
|
229
|
+
// when the research wrote it, to the second in UTC (the app shows the local day; DATE stays the day): every app takes
|
|
230
|
+
// its first ten characters as the day before (N23)
|
|
229
231
|
if (opts.research)
|
|
230
|
-
w.line(1, "_STROM_ASOF",
|
|
232
|
+
w.line(1, "_STROM_ASOF", asOfNow());
|
|
231
233
|
// an archive (no agent; the app's data written as they come): for an app that knows it (APP_KNOWS_ARCHIVE)
|
|
232
234
|
if (opts.archive && tree.config.mode === "archive")
|
|
233
235
|
w.line(1, "_STROM_MODE", "archive");
|
|
236
|
+
// the file says the bounds of a lineage the person set, all of them (none: none), for an app that keeps them
|
|
237
|
+
if (opts.lines)
|
|
238
|
+
w.line(1, "_STROM_LINES", "Y");
|
|
234
239
|
}
|
|
235
240
|
w.line(1, "SUBM", "@U1@");
|
|
236
241
|
w.line(1, "GEDC");
|
|
@@ -301,6 +306,10 @@ export function exportGedcom(tree, opts = {}) {
|
|
|
301
306
|
}
|
|
302
307
|
}
|
|
303
308
|
w.line(1, "SEX", p.sex);
|
|
309
|
+
// the person's bound of a lineage (the Strom profile, an app that keeps it)
|
|
310
|
+
const line = opts.for === "strom" ? opts.lines?.get(p.id) : undefined;
|
|
311
|
+
if (line)
|
|
312
|
+
w.line(1, "_STROM_LINE", line);
|
|
304
313
|
w.line(1, "REFN", p.id);
|
|
305
314
|
w.line(2, "TYPE", REFN_TYPE);
|
|
306
315
|
const deferred = [];
|
|
@@ -953,3 +962,7 @@ function gedDate(d) {
|
|
|
953
962
|
const m = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"][d.getUTCMonth()];
|
|
954
963
|
return `${d.getUTCDate()} ${m} ${d.getUTCFullYear()}`;
|
|
955
964
|
}
|
|
965
|
+
/** When the research wrote the file (1 _STROM_ASOF): ISO 8601 to the second in UTC ("2026-10-31T06:12:05Z"). */
|
|
966
|
+
export function asOfNow(d = new Date()) {
|
|
967
|
+
return `${d.toISOString().slice(0, 19)}Z`;
|
|
968
|
+
}
|
package/dist/gedcom/validate.js
CHANGED
|
@@ -14,12 +14,12 @@ const INDI_EVENTS = [
|
|
|
14
14
|
const FAM_EVENTS = ["MARR", "DIV", "MARB", "MARC", "MARL", "MARS", "ANUL", "DIVF", "ENGA", "CENS", "RESI", "EVEN", "NCHI"];
|
|
15
15
|
/** Allowed child tags by parent context ("INDI", "INDI.BIRT", …). CONC/CONT are allowed under anything with text. */
|
|
16
16
|
const CHILDREN = {
|
|
17
|
-
HEAD: ["SOUR", "DEST", "DATE", "SUBM", "SUBN", "FILE", "COPR", "GEDC", "CHAR", "LANG", "PLAC", "NOTE", "_STROM_TREE", "_STROM_HEAD", "_STROM_LINKS", "_STROM_ASOF", "_STROM_MODE"],
|
|
17
|
+
HEAD: ["SOUR", "DEST", "DATE", "SUBM", "SUBN", "FILE", "COPR", "GEDC", "CHAR", "LANG", "PLAC", "NOTE", "_STROM_TREE", "_STROM_HEAD", "_STROM_LINKS", "_STROM_ASOF", "_STROM_MODE", "_STROM_LINES"],
|
|
18
18
|
"HEAD.SOUR": ["VERS", "NAME", "CORP", "DATA"],
|
|
19
19
|
"HEAD._STROM_LINKS": ["_SCHEME"],
|
|
20
20
|
"HEAD.GEDC": ["VERS", "FORM"],
|
|
21
21
|
"HEAD.DATE": ["TIME"],
|
|
22
|
-
INDI: ["NAME", "SEX", "FAMC", "FAMS", "NOTE", "SOUR", "REFN", "ASSO", "ALIA", "OBJE", "RESN", "RIN", "CHAN", "_STORY", "_STROM_CONFLICT", "_STROM_HYPO", "_STROM_SEARCHED", "_STROM_EDGE", "_STROM_ISLAND", ...INDI_EVENTS],
|
|
22
|
+
INDI: ["NAME", "SEX", "FAMC", "FAMS", "NOTE", "SOUR", "REFN", "ASSO", "ALIA", "OBJE", "RESN", "RIN", "CHAN", "_STORY", "_STROM_CONFLICT", "_STROM_HYPO", "_STROM_SEARCHED", "_STROM_EDGE", "_STROM_ISLAND", "_STROM_LINE", ...INDI_EVENTS],
|
|
23
23
|
"INDI._STROM_CONFLICT": ["TYPE", "TITL", "STAT", "_STROM_TAKE", "VAL", "DECI"],
|
|
24
24
|
"INDI._STROM_HYPO": ["TITL", "STAT", "_CHOSEN", "NOTE", "_VAR"],
|
|
25
25
|
// what a variant of a hypothesis would connect (the Strom profile)
|
|
@@ -82,6 +82,8 @@ const EXTENSIONS = new Set([
|
|
|
82
82
|
"_STROM_READ", "_STROM_VERIFIED", "_STROM_READING", "_STROM_ENTRY", "_STROM_STATUS",
|
|
83
83
|
// an excerpt's original by its content, and how its picture lies
|
|
84
84
|
"_STROM_SHA", "_STROM_ORIENT",
|
|
85
|
+
// the bounds of a lineage the person set (on a person), and the header's mark that the file says them
|
|
86
|
+
"_STROM_LINE", "_STROM_LINES",
|
|
85
87
|
]);
|
|
86
88
|
/** Context of the children of the last tag in `anc` (the ancestors of a line). */
|
|
87
89
|
function contextOf(anc) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strom-research",
|
|
3
|
-
"version": "1.24.0-beta.
|
|
3
|
+
"version": "1.24.0-beta.2",
|
|
4
4
|
"description": "Genealogical research toolkit for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"runtime": {
|
|
22
22
|
"name": "node",
|
|
23
23
|
"version": ">=24",
|
|
24
|
-
"onFail": "
|
|
24
|
+
"onFail": "warn"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|