strom-research 1.18.0-beta.9 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.cs.md CHANGED
@@ -7,7 +7,7 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
7
7
 
8
8
  ## 1.18.0
9
9
 
10
- *Připravuje se: bety do 1.18.0-beta.9. Oddíl bude úplný s vydáním.*
10
+ *21. 10. 2026 · patří k aplikaci Strom 3.14.0.*
11
11
 
12
12
  - **Fotky z telefonu nastojato.** Fotka otočená podle údaje EXIF se čte tak, jak se má dívat, nastojato; místa na ní
13
13
  vyznačená (výřezy, poloviny dvoustrany, uložená část) jsou místa obrázku, jak se ukazuje. Fotka sama zůstává, jak
@@ -16,14 +16,17 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
16
16
  na macOS, Windows i Linuxu — a originál zůstává, jak přišel. Fotka HEIC jde do aplikace Strom jako výřez JPEG.
17
17
  Kde obrázek na počítači nic nepřevede, řekne to jedna věta i s tím, co by pomohlo; `strom tidy` převedené obrázky
18
18
  po 30 dnech nebo nad 500 MB uklidí.
19
- - **Jeden dokument přečtený naráz.** `strom document read` přečte fotku, sken nebo PDF jednoho dokumentu — zápis
20
- z matriky, dopis, listinu, náhrobek, rub fotky: přepis slovo od slova, překlad, význam starých slov a zkratek
21
- a osoby, které jmenuje, i s tím, kdo ze stromu to může být. Nic se nedomýšlí: jméno, které v dokumentu není,
22
- vypadne — i příjmení, nikdy se nebere od otce ani z jiné rodiny —, co nejde přečíst, je označené; druhý čtenář
23
- může číst naslepo. Do stromu se z toho nic nezapíše samo. Dokument už přečtený to o sobě řekne, takže se omylem
24
- nečte podruhé. Dokument čtený znovu se ukazuje pod názvem svého souboru, nikdy pod kódem.
25
- Dokument poslaný z aplikace Strom najde agenta stejně jako terminál — i nainstalovaného do vlastní složky, přes npm
26
- nebo Homebrew — místo hlášení, že agent není nastavený.
19
+ - **Jeden dokument přečtený naráz.** `strom document read` přečte fotku, sken nebo PDF jednoho dokumentu — zápis z
20
+ matriky, dopis, listinu, náhrobek, rub fotky: přepis slovo od slova, překlad, význam starých slov a zkratek a osoby,
21
+ které jmenuje, i s tím, kdo ze stromu to může být. Nic se nedomýšlí: jméno, které v dokumentu není, vypadne — i
22
+ příjmení, nikdy se nebere od otce ani z jiné rodiny —, co nejde přečíst, je označené; druhý čtenář může číst naslepo.
23
+ Dokument na úředním formuláři, jako rodný list, si své osoby ponechá — dítě i oba rodiče s jejich podílem, i jméno a
24
+ příjmení psané proloženě a rodné příjmení po „roz.“ —, a dokument přečtený dřív je po aktualizaci ukáže, aniž by se
25
+ četl znovu. U každé osoby a události je i místo na stránce, kde stojí, takže aplikace Strom ukáže výřez každé z nich.
26
+ Do stromu se z toho nic nezapíše samo. Dokument už přečtený to o sobě řekne, takže se omylem nečte podruhé. Dokument
27
+ čtený znovu se ukazuje pod názvem svého souboru, nikdy pod kódem. Dokument poslaný z aplikace Strom najde agenta
28
+ stejně jako terminál — i nainstalovaného do vlastní složky, přes npm nebo Homebrew — místo hlášení, že agent není
29
+ nastavený. Čte ho každý agent, se kterým výzkum pracuje, s modelem a úsilím nastaveným pro výzkum.
27
30
  - **Fotky z telefonu.** `strom phone on`, které zapíná jen člověk, pustí k počítači telefon v domácí síti (Wi-Fi
28
31
  nebo kabel — nikdy mobilní síť ani VPN). Telefon se jednou spáruje přes QR kód; jeho stránka pak fotí nebo vybírá
29
32
  z galerie a posílá fotky celé do stromu výzkumu, s poznámkou a osobou, kterou ukazují — stejně jako „Přidat
@@ -55,9 +58,12 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
55
58
  jde vrátit nulou. Most už nedrží složku rodokmenu, takže ji jde na Windows smazat, a sám skončí, když rodokmen zmizí
56
59
  (rodokmen smaže `strom trees remove`). Konec stromu, kde křest neuvádí otce, je uzavřený, jakmile na něm nic neběží
57
60
  – už nečeká natrvalo na rozhodnutí. Pramen přejmenovaný v aplikaci Strom se přejmenuje i ve výzkumu a signatura nebo
58
- strana pramene přijde do aplikace k prameni samému („Signatura / strana“), i když ho zatím nic necituje. S
59
- `excerpts.for all` dostane v aplikaci Strom výřez i pramen, který zatím nikdo necituje, pokud se vedle citovaných
60
- vejde do limitu – citované se kvůli němu nezmenší; co se nevešlo, se řekne.
61
+ strana pramene přijde do aplikace k prameni samému („Signatura / strana“), i když ho zatím nic necituje. Emoji nebo
62
+ jiný vzácný znak, který soubor z aplikace Strom cestou poškodil (ukáže se jako ��), není přejmenování ani změna
63
+ poznámky: výzkum si nechá svůj text. Místo nebo jiný údaj delší než řádek souboru GEDCOM (255 bajtů) už nezastaví
64
+ jeho zápis a žádné emoji se nerozdělí tam, kde dlouhý řádek pokračuje. S `excerpts.for all` dostane v aplikaci Strom
65
+ výřez i pramen, který zatím nikdo necituje, pokud se vedle citovaných vejde do limitu – citované se kvůli němu
66
+ nezmenší; co se nevešlo, se řekne.
61
67
 
62
68
  ## 1.17.0
63
69
 
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.18.0
9
9
 
10
- *In Vorbereitung: die Betas bis 1.18.0-beta.9. Der Abschnitt ist mit dem Release vollständig.*
10
+ *21.10.2026 · gehört zur Strom-App 3.14.0.*
11
11
 
12
12
  - **Handyfotos aufrecht.** Ein Foto, das nach seiner EXIF-Ausrichtung gedreht ist, wird gelesen, wie es gesehen
13
13
  werden soll, aufrecht; die darauf markierten Stellen (Ausschnitte, die Hälften einer Doppelseite, ein gespeicherter
@@ -16,16 +16,21 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
16
16
  umgewandelt — unter macOS, Windows und Linux —, das Original bleibt, wie es kam. Ein HEIC-Foto geht als
17
17
  JPEG-Ausschnitt in die Strom-App. Wo nichts auf dem Computer ein Bild umwandelt, sagt es ein Satz, auch was helfen
18
18
  würde; `strom tidy` räumt umgewandelte Bilder nach 30 Tagen oder über 500 MB weg.
19
- - **Ein Dokument auf einmal gelesen.** `strom document read` liest ein Foto, einen Scan oder eine PDF eines
20
- Dokuments — einen Matrikeleintrag, einen Brief, eine Urkunde, einen Grabstein, die Rückseite eines Fotos: die
21
- Abschrift Wort für Wort, ihre Übersetzung, was alte Wörter und Abkürzungen bedeuten, und die genannten Personen,
22
- samt wem im Stammbaum sie entsprechen können. Nichts wird ergänzt: ein Name, der nicht im Dokument steht, fällt
23
- weg — auch ein Nachname, nie vom Vater oder aus einer anderen Familie genommen —, Unleserliches wird markiert; ein
24
- zweiter Leser kann blind lesen. In den Stammbaum gelangt nichts von selbst. Ein schon gelesenes Dokument sagt es,
25
- so wird es nicht versehentlich ein zweites Mal gelesen. Ein erneut gelesenes Dokument wird unter dem Namen seiner
26
- Datei gezeigt, nie unter einem Code. Ein aus der Strom-App gesendetes Dokument findet den Agenten wie das Terminal —
27
- auch einen in einem eigenen Ordner, über npm oder Homebrew installierten —, statt zu melden, es sei kein Agent
28
- eingerichtet.
19
+ - **Ein Dokument auf einmal gelesen.** `strom document read` liest ein Foto, einen Scan oder eine PDF eines Dokuments —
20
+ einen Matrikeleintrag, einen Brief, eine Urkunde, einen Grabstein, die Rückseite eines Fotos: die Abschrift Wort für
21
+ Wort, ihre Übersetzung, was alte Wörter und Abkürzungen bedeuten, und die genannten Personen, samt wem im Stammbaum
22
+ sie entsprechen können. Nichts wird ergänzt: ein Name, der nicht im Dokument steht, fällt weg — auch ein Nachname, nie
23
+ vom Vater oder aus einer anderen Familie genommen —, Unleserliches wird markiert; ein zweiter Leser kann blind lesen.
24
+ Ein Dokument auf einem amtlichen Formular, etwa eine Geburtsurkunde, behält seine Personen — das Kind und beide Eltern
25
+ mit ihrem Anteil, auch ein gesperrt getippter Name und ein Geburtsname nach „geb.“ —, und ein früher gelesenes
26
+ Dokument zeigt sie nach dem Update, ohne erneut gelesen zu werden. Jede Person und jedes Ereignis kommt mit der Stelle
27
+ auf der Seite, an der es steht, so zeigt die Strom-App von jedem
28
+ einen eigenen Ausschnitt.
29
+ In den Stammbaum gelangt nichts von selbst. Ein schon gelesenes Dokument sagt es, so wird es nicht versehentlich ein
30
+ zweites Mal gelesen. Ein erneut gelesenes Dokument wird unter dem Namen seiner Datei gezeigt, nie unter einem Code.
31
+ Ein aus der Strom-App gesendetes Dokument findet den Agenten wie das Terminal — auch einen in einem eigenen Ordner,
32
+ über npm oder Homebrew installierten —, statt zu melden, es sei kein Agent eingerichtet. Jeder Agent, mit dem die
33
+ Forschung arbeitet, liest es, mit dem Modell und dem Aufwand, die für die Forschung eingestellt sind.
29
34
  - **Fotos vom Handy.** `strom phone on`, das nur ein Mensch einschaltet, lässt ein Handy im Heimnetz (WLAN oder
30
35
  Kabel — nie ein Mobilfunknetz oder ein VPN) zu diesem Computer. Das Handy wird einmal per QR-Code gekoppelt; seine
31
36
  Seite fotografiert dann oder wählt aus der Galerie und schickt die Fotos ganz in einen Stammbaum der Forschung, mit
@@ -64,9 +69,13 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
64
69
  Stammbaum weg ist (einen Stammbaum löscht `strom trees remove`). Ein Ende des Baums, wo die Taufe keinen Vater
65
70
  nennt, ist ein geschlossenes Ende, sobald nichts mehr daran läuft – es wartet nicht mehr für immer auf eine
66
71
  Entscheidung. Eine in der Strom-App umbenannte Quelle heißt auch in der Forschung neu, und Signatur oder Seite einer
67
- Quelle kommen in der App bei der Quelle selbst an, auch bevor etwas sie zitiert. Mit `excerpts.for all` bekommt auch
68
- eine Quelle, die noch niemand zitiert, in der Strom-App ihren Ausschnitt, soweit er neben den zitierten unter die
69
- Grenze passt – die zitierten werden dafür nie kleiner; was nicht passte, wird gesagt.
72
+ Quelle kommen in der App bei der Quelle selbst an, auch bevor etwas sie zitiert. Ein Emoji oder ein anderes seltenes
73
+ Zeichen, das die Datei der Strom-App unterwegs beschädigt hat (als �� zu sehen), ist keine Umbenennung und keine
74
+ geänderte Notiz: Die Forschung behält ihren Text. Ein Ort oder ein anderer Wert, länger als eine Zeile der
75
+ GEDCOM-Datei (255 Bytes), hält das Schreiben der Datei nicht mehr auf, und kein Emoji wird dort geteilt, wo eine
76
+ lange Zeile weitergeht. Mit `excerpts.for all` bekommt auch eine Quelle, die noch niemand zitiert, in der Strom-App
77
+ ihren Ausschnitt, soweit er neben den zitierten unter die Grenze passt – die zitierten werden dafür nie kleiner; was
78
+ nicht passte, wird gesagt.
70
79
 
71
80
  ## 1.17.0
72
81
 
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.18.0
9
9
 
10
- *In preparation: the betas up to 1.18.0-beta.9. The section is complete with the release.*
10
+ *2026-10-21 · goes with the Strom app 3.14.0.*
11
11
 
12
12
  - **Phone photos upright.** A photo turned by its EXIF orientation is read as it is meant to be seen, upright; the
13
13
  places marked on it (excerpts, the halves of a spread, a cut-out part) are of the picture as it is shown. The
@@ -18,12 +18,17 @@ The changes of each version in short. The full notes and the install lines are o
18
18
  help; `strom tidy` clears the converted pictures away after 30 days or above 500 MB.
19
19
  - **One document read at once.** `strom document read` reads a photo, a scan or a PDF of one document — a register
20
20
  entry, a letter, a deed, a gravestone, the back of a photo: the transcript word for word, its translation, what its
21
- old words and abbreviations mean, and the people it names, with who of the tree they may be. Nothing is filled in:
22
- a name the document does not have is left out — a surname too, never taken from a father or another family —,
23
- what cannot be read is marked; a second reader can read it blind. Nothing of it enters the tree by itself. A
24
- document read already says so, so it is not read a second time by mistake. A document read again is shown under
25
- the name of its file, never a code. A document sent from the Strom app finds the agent as the terminal does — one
26
- installed in a folder of its own, by npm or Homebrew too — instead of saying no agent is set up.
21
+ old words and abbreviations mean, and the people it names, with who of the tree they may be. Nothing is filled in: a
22
+ name the document does not have is left out — a surname too, never taken from a father or another family —, what
23
+ cannot be read is marked; a second reader can read it blind. A
24
+ document on a printed form, as a birth certificate, keeps its people — the child and both parents with their part in
25
+ it, their names typed letter-spaced and a mother's birth name after "née" or "roz." too —, and a document read before
26
+ shows them after the update with nothing read again. Each person and event it names comes with the place on the page
27
+ where it stands, so the Strom app shows each its own excerpt. Nothing of it enters the tree by itself. A document read
28
+ already says so, so it is not read a second time by mistake. A document read again is shown under the name of its
29
+ file, never a code. A document sent from the Strom app finds the agent as the terminal does — one installed in a
30
+ folder of its own, by npm or Homebrew too — instead of saying no agent is set up. Every agent the research works with
31
+ reads it, with the model and effort set for the research.
27
32
  - **Photos from a phone.** `strom phone on`, turned on by the person alone, lets a phone on the home network (Wi-Fi
28
33
  or a cable — never a mobile network or a VPN) reach this computer. The phone is paired once by its QR code; its
29
34
  page then takes photos or picks them from the gallery and sends them whole into a tree of the research, with a
@@ -59,9 +64,12 @@ The changes of each version in short. The full notes and the install lines are o
59
64
  Windows can delete it, and ends by itself once the tree is gone (`strom trees remove` deletes a tree). An end of the
60
65
  tree where the baptism names no father is a closed end once nothing runs on it, no longer waiting for a decision for
61
66
  good. A source renamed in the Strom app is renamed in the research too, and a source's call number or page reaches
62
- the app on the source itself, also before anything cites it. With `excerpts.for all` a source nobody cites yet gets
63
- its excerpt in the Strom app too, wherever it fits under the limit beside the cited ones, which never get smaller
64
- for it; what did not fit is said.
67
+ the app on the source itself, also before anything cites it. An emoji or another rare sign the Strom app's file
68
+ damaged on its way (shown as ��) is no rename and no changed note: the research keeps its text. A place or another
69
+ value longer than a line of the GEDCOM file (255 bytes) no longer stops the file from being written, and no emoji is
70
+ split where a long line goes on. With `excerpts.for all` a source nobody cites yet gets its excerpt in the Strom app
71
+ too, wherever it fits under the limit beside the cited ones, which never get smaller for it; what did not fit is
72
+ said.
65
73
 
66
74
  ## 1.17.0
67
75
 
@@ -925,7 +925,7 @@
925
925
  "ui.show.age": "věk {age}",
926
926
  "ui.show.ages": "věk {ages}",
927
927
  "ui.show.with": "uvedeni: {who}",
928
- "ui.show.retracted": "(staženo: {reason})",
928
+ "ui.show.retracted": "(odvoláno: {reason})",
929
929
  "ui.show.relof": "{rel} u {who}",
930
930
  "ui.show.sex.M": "muž",
931
931
  "ui.show.sex.F": "žena",
@@ -1315,12 +1315,12 @@
1315
1315
  "ui.phone.qr.url": "adresa pod kódem: {url}",
1316
1316
  "ui.phone.qr.more": "další adresa tohoto počítače: strom phone qr --address {url}",
1317
1317
  "ui.phone.qr.off": "propojení s telefonem je vypnuté: QR kód žádný (strom phone on)",
1318
- "ui.phone.qr.app": "QR kód ke spárování telefonu: v aplikaci Strom (Výzkum → Připojit telefon…), nebo strom phone qr v terminálu",
1318
+ "ui.phone.qr.app": "QR kód ke spárování telefonu: v aplikaci Strom (Výzkum → Další → Připojit telefon…), nebo strom phone qr v terminálu",
1319
1319
  "ui.phone.notturned": "propojení s telefonem: nezapnuto, nic se nezměnilo",
1320
1320
  "ui.error.phone.off": "propojení s telefonem je vypnuté",
1321
- "ui.error.phone.off.hint": "zapíná se příkazem strom phone on v terminálu, nebo v aplikaci Strom (Výzkum → Připojit telefon…)",
1321
+ "ui.error.phone.off.hint": "zapíná se příkazem strom phone on v terminálu, nebo v aplikaci Strom (Výzkum → Další → Připojit telefon…)",
1322
1322
  "ui.error.phone.qr-terminal": "QR kód ke spárování telefonu se ukazuje jen v terminálu nebo v aplikaci Strom",
1323
- "ui.error.phone.qr-terminal.hint": "strom phone qr v terminálu, nebo Výzkum → Připojit telefon… v aplikaci Strom",
1323
+ "ui.error.phone.qr-terminal.hint": "strom phone qr v terminálu, nebo Výzkum → Další → Připojit telefon… v aplikaci Strom",
1324
1324
  "ui.error.phone.address": "server telefonu na tomto počítači nemá adresu {address}",
1325
1325
  "ui.error.phone.address.hint": "jeho adresy: {addresses}",
1326
1326
  "ui.phone.page.title": "Výzkum Strom",
@@ -1328,14 +1328,14 @@
1328
1328
  "ui.phone.page.paired.title": "Spárováno",
1329
1329
  "ui.phone.page.paired": "Telefon je spárovaný s výzkumem na počítači. Spárovaný zůstane, dokud se na počítači neodpojí.",
1330
1330
  "ui.phone.page.expired.title": "Kód už neplatí",
1331
- "ui.phone.page.expired": "QR kód spáruje jeden telefon a platí 10 minut. Nový ukáže aplikace Strom na počítači (Výzkum → Připojit telefon…).",
1331
+ "ui.phone.page.expired": "QR kód spáruje jeden telefon a platí 10 minut. Nový ukáže aplikace Strom na počítači (Výzkum → Další → Připojit telefon…).",
1332
1332
  "ui.phone.page.unreachable.title": "Počítač neodpovídá",
1333
1333
  "ui.phone.page.unreachable": "Je počítač zapnutý, propojení s telefonem zapnuté a telefon ve stejné Wi-Fi?",
1334
1334
  "ui.phone.page.retry": "Zkusit znovu",
1335
1335
  "ui.phone.page.unpaired.title": "Telefon není spárovaný s tímto počítačem",
1336
1336
  "ui.phone.page.unpaired.why": "Telefon byl na počítači odpojen, nebo se změnila adresa počítače, nebo je stránka otevřená bez QR kódu.",
1337
- "ui.phone.page.unpaired.how": "Na počítači v aplikaci Strom: Výzkum → Připojit telefon a načíst QR kód.",
1338
- "ui.phone.page.unpaired.howHome": "V ikoně na ploše QR kód načíst nejde. Stránku otevřít v prohlížeči telefonu (Safari, Chrome) načtením QR kódu fotoaparátem (na počítači v aplikaci Strom: Výzkum → Připojit telefon) a odtud ikonu na plochu přidat znovu.",
1337
+ "ui.phone.page.unpaired.how": "Na počítači v aplikaci Strom: Výzkum → Další → Připojit telefon a načíst QR kód.",
1338
+ "ui.phone.page.unpaired.howHome": "V ikoně na ploše QR kód načíst nejde. Stránku otevřít v prohlížeči telefonu (Safari, Chrome) načtením QR kódu fotoaparátem (na počítači v aplikaci Strom: Výzkum → Další → Připojit telefon) a odtud ikonu na plochu přidat znovu.",
1339
1339
  "ui.phone.page.unpaired.queue": "{n, plural, one {# neodeslaná fotka čeká v tomto telefonu} few {# neodeslané fotky čekají v tomto telefonu} other {# neodeslaných fotek čeká v tomto telefonu}}",
1340
1340
  "ui.phone.page.unpaired.queueBody": "Po novém spárování s tímto počítačem se odešlou samy.",
1341
1341
  "ui.phone.page.ago.s": "před {n} s",
@@ -1766,7 +1766,7 @@
1766
1766
  "ui.move.cancel": "Zrušit",
1767
1767
  "ui.move.cancelled": "Nic se nezaložilo.",
1768
1768
  "ui.move.without": "Pokračovat bez prohlížeče: výzkum (nebo Archiv Stromu) ze souboru",
1769
- "ui.move.without.what": "Aplikace v {from} se k výzkumu nepřipojí: nic z aplikace nepůjde živě – ani výzkum, ani Archiv Stromu. Změny z aplikace jdou do výzkumu souborem (v aplikaci Export → GEDCOM, ve výzkumu strom sync <soubor>), verze výzkumu se do aplikace vrací importem souboru a fotky touto cestou nejdou.",
1769
+ "ui.move.without.what": "Aplikace v {from} se k výzkumu nepřipojí: nic z aplikace nepůjde živě – ani výzkum, ani Archiv Stromu. Změny z aplikace jdou do výzkumu souborem (v aplikaci Export… → GEDCOM, ve výzkumu strom sync <soubor>), verze výzkumu se do aplikace vrací importem souboru a fotky touto cestou nejdou.",
1770
1770
  "ui.move.without.later.here": "Plné spojení je jen přes Chrome, Edge, Brave nebo Firefox a {browser} tu je: strom app (v menu Aplikace Strom) v něm kdykoli otevře výzkum a přenos se tím dokončí.",
1771
1771
  "ui.move.without.later.none": "Plné spojení je jen přes Chrome, Edge, Brave nebo Firefox: po instalaci některého z nich strom app (v menu Aplikace Strom) otevře výzkum v něm a přenos se tím dokončí.",
1772
1772
  "ui.move.nobrowser": "Na tomto počítači není prohlížeč, ze kterého se aplikace Strom k výzkumu připojí. Strom se může přenést do prohlížeče, který to umí – Google Chrome nebo Microsoft Edge, ke stažení zdarma.",
@@ -2388,8 +2388,10 @@
2388
2388
  "ui.error.family.needs-reason.hint": "např. --reason \"křest uvádí Jana jako otčíma\"",
2389
2389
  "ui.error.person.needs-reason": "změna jména, jeho titulu nebo známého pohlaví potřebuje --reason",
2390
2390
  "ui.error.person.needs-reason.hint": "např. --reason \"špatně přečteno: matrika má Novák, ne Nowak\"",
2391
- "ui.error.retract.needs-reason": "stažení potřebuje --reason",
2391
+ "ui.error.retract.needs-reason": "odvolání potřebuje --reason",
2392
2392
  "ui.error.retract.needs-reason.hint": "např. --reason \"špatně přečteno: zápis uvádí Annu, ne Antona\"",
2393
+ "ui.error.name.retracted": "jméno {name} u {id} je odvolané: {reason}",
2394
+ "ui.error.name.retracted.hint": "přece správné: strom name restore {id} \"{name}\" --reason \"…\"",
2393
2395
  "ui.error.merge.needs-reason": "sloučení potřebuje --reason",
2394
2396
  "ui.error.merge.needs-reason.hint": "--reason \"<proč>\"",
2395
2397
  "ui.error.record.needs-reason": "u {id} se mění {fields} — chybí --reason",
@@ -1315,12 +1315,12 @@
1315
1315
  "ui.phone.qr.url": "die Adresse unter dem Code: {url}",
1316
1316
  "ui.phone.qr.more": "eine weitere Adresse dieses Computers: strom phone qr --address {url}",
1317
1317
  "ui.phone.qr.off": "die Verbindung mit dem Telefon ist ausgeschaltet: kein QR-Code (strom phone on)",
1318
- "ui.phone.qr.app": "der QR-Code zum Koppeln eines Telefons: in der Strom-App (Forschung → Telefon verbinden…) oder strom phone qr im Terminal",
1318
+ "ui.phone.qr.app": "der QR-Code zum Koppeln eines Telefons: in der Strom-App (Forschung → Weitere Aktionen → Telefon verbinden…) oder strom phone qr im Terminal",
1319
1319
  "ui.phone.notturned": "Verbindung mit dem Telefon: nicht eingeschaltet, nichts geändert",
1320
1320
  "ui.error.phone.off": "die Verbindung mit dem Telefon ist ausgeschaltet",
1321
- "ui.error.phone.off.hint": "eingeschaltet wird sie mit strom phone on in einem Terminal oder in der Strom-App (Forschung → Telefon verbinden…)",
1321
+ "ui.error.phone.off.hint": "eingeschaltet wird sie mit strom phone on in einem Terminal oder in der Strom-App (Forschung → Weitere Aktionen → Telefon verbinden…)",
1322
1322
  "ui.error.phone.qr-terminal": "der QR-Code zum Koppeln eines Telefons wird nur in einem Terminal oder in der Strom-App gezeigt",
1323
- "ui.error.phone.qr-terminal.hint": "strom phone qr in einem Terminal oder Forschung → Telefon verbinden… in der Strom-App",
1323
+ "ui.error.phone.qr-terminal.hint": "strom phone qr in einem Terminal oder Forschung → Weitere Aktionen → Telefon verbinden… in der Strom-App",
1324
1324
  "ui.error.phone.address": "der Telefon-Server dieses Computers hat keine Adresse {address}",
1325
1325
  "ui.error.phone.address.hint": "seine Adressen: {addresses}",
1326
1326
  "ui.phone.page.title": "Strom-Forschung",
@@ -1328,14 +1328,14 @@
1328
1328
  "ui.phone.page.paired.title": "Gekoppelt",
1329
1329
  "ui.phone.page.paired": "Das Telefon ist mit der Forschung auf dem Computer gekoppelt. Es bleibt gekoppelt, bis es auf dem Computer getrennt wird.",
1330
1330
  "ui.phone.page.expired.title": "Der Code gilt nicht mehr",
1331
- "ui.phone.page.expired": "Ein QR-Code koppelt ein Telefon und gilt 10 Minuten. Einen neuen zeigt die Strom-App auf dem Computer (Forschung → Telefon verbinden…).",
1331
+ "ui.phone.page.expired": "Ein QR-Code koppelt ein Telefon und gilt 10 Minuten. Einen neuen zeigt die Strom-App auf dem Computer (Forschung → Weitere Aktionen → Telefon verbinden…).",
1332
1332
  "ui.phone.page.unreachable.title": "Der Computer antwortet nicht",
1333
1333
  "ui.phone.page.unreachable": "Ist der Computer eingeschaltet, die Verbindung mit dem Telefon eingeschaltet und das Telefon im selben WLAN?",
1334
1334
  "ui.phone.page.retry": "Erneut versuchen",
1335
1335
  "ui.phone.page.unpaired.title": "Dieses Telefon ist nicht mit diesem Computer gekoppelt",
1336
1336
  "ui.phone.page.unpaired.why": "Das Telefon wurde am Computer getrennt, die Adresse des Computers hat sich geändert, oder die Seite wurde ohne QR-Code geöffnet.",
1337
- "ui.phone.page.unpaired.how": "Am Computer in der Strom-App: Forschung → Telefon verbinden und den QR-Code scannen.",
1338
- "ui.phone.page.unpaired.howHome": "Im Symbol auf dem Home-Bildschirm lässt sich kein QR-Code scannen. Die Seite im Browser des Telefons öffnen (Safari, Chrome), indem der QR-Code mit der Kamera gescannt wird (am Computer in der Strom-App: Forschung → Telefon verbinden), und von dort das Symbol erneut zum Home-Bildschirm hinzufügen.",
1337
+ "ui.phone.page.unpaired.how": "Am Computer in der Strom-App: Forschung → Weitere Aktionen → Telefon verbinden und den QR-Code scannen.",
1338
+ "ui.phone.page.unpaired.howHome": "Im Symbol auf dem Home-Bildschirm lässt sich kein QR-Code scannen. Die Seite im Browser des Telefons öffnen (Safari, Chrome), indem der QR-Code mit der Kamera gescannt wird (am Computer in der Strom-App: Forschung → Weitere Aktionen → Telefon verbinden), und von dort das Symbol erneut zum Home-Bildschirm hinzufügen.",
1339
1339
  "ui.phone.page.unpaired.queue": "{n, plural, one {# ungesendetes Foto wartet auf diesem Telefon} other {# ungesendete Fotos warten auf diesem Telefon}}",
1340
1340
  "ui.phone.page.unpaired.queueBody": "Nach erneutem Koppeln mit diesem Computer werden sie von selbst gesendet.",
1341
1341
  "ui.phone.page.ago.s": "vor {n} s",
@@ -1766,7 +1766,7 @@
1766
1766
  "ui.move.cancel": "Abbrechen",
1767
1767
  "ui.move.cancelled": "Nichts wurde angelegt.",
1768
1768
  "ui.move.without": "Ohne Browser weiter: die Forschung (oder ein Strom-Archiv) aus der Datei",
1769
- "ui.move.without.what": "Die App in {from} verbindet sich nicht mit der Forschung: nichts aus der App geht live – weder eine Forschung noch ein Strom-Archiv. Änderungen aus der App gehen als Datei in die Forschung (in der App Export → GEDCOM, in der Forschung strom sync <Datei>), die Version der Forschung kommt per Dateiimport zurück, und Fotos gehen diesen Weg nicht.",
1769
+ "ui.move.without.what": "Die App in {from} verbindet sich nicht mit der Forschung: nichts aus der App geht live – weder eine Forschung noch ein Strom-Archiv. Änderungen aus der App gehen als Datei in die Forschung (in der App Export… → GEDCOM, in der Forschung strom sync <Datei>), die Version der Forschung kommt per Dateiimport zurück, und Fotos gehen diesen Weg nicht.",
1770
1770
  "ui.move.without.later.here": "Die volle Verbindung gibt es nur über Chrome, Edge, Brave oder Firefox, und {browser} ist hier: strom app (im Menü die Strom-App) öffnet die Forschung jederzeit darin, und die Übertragung ist abgeschlossen.",
1771
1771
  "ui.move.without.later.none": "Die volle Verbindung gibt es nur über Chrome, Edge, Brave oder Firefox: sobald einer installiert ist, öffnet strom app (im Menü die Strom-App) die Forschung dort, und die Übertragung ist abgeschlossen.",
1772
1772
  "ui.move.nobrowser": "Auf diesem Computer gibt es keinen Browser, aus dem sich die Strom-App mit der Forschung verbindet. Der Stammbaum kann in einen Browser umziehen, der es kann – Google Chrome oder Microsoft Edge, kostenlos herunterzuladen.",
@@ -2390,6 +2390,8 @@
2390
2390
  "ui.error.person.needs-reason.hint": "z. B. --reason \"falsch gelesen: im Register steht Novák, nicht Nowak\"",
2391
2391
  "ui.error.retract.needs-reason": "Zurückziehen braucht --reason",
2392
2392
  "ui.error.retract.needs-reason.hint": "z. B. --reason \"falsch gelesen: der Eintrag nennt Anna, nicht Anton\"",
2393
+ "ui.error.name.retracted": "der Name {name} von {id} ist zurückgezogen: {reason}",
2394
+ "ui.error.name.retracted.hint": "doch richtig: strom name restore {id} \"{name}\" --reason \"…\"",
2393
2395
  "ui.error.merge.needs-reason": "Zusammenführen braucht --reason",
2394
2396
  "ui.error.merge.needs-reason.hint": "--reason \"<warum>\"",
2395
2397
  "ui.error.record.needs-reason": "bei {id} ändert sich {fields} — --reason fehlt",
package/dist/cli/ui.js CHANGED
@@ -1215,12 +1215,12 @@ export const UI = {
1215
1215
  "ui.phone.qr.url": "the address under the code: {url}",
1216
1216
  "ui.phone.qr.more": "another address of this computer: strom phone qr --address {url}",
1217
1217
  "ui.phone.qr.off": "the phone connection is off: no QR code (strom phone on)",
1218
- "ui.phone.qr.app": "the QR code to pair a phone: in the Strom app (Research → Connect phone…), or strom phone qr in a terminal",
1218
+ "ui.phone.qr.app": "the QR code to pair a phone: in the Strom app (Research → Other actions → Connect phone…), or strom phone qr in a terminal",
1219
1219
  "ui.phone.notturned": "phone connection: not turned on, nothing changed",
1220
1220
  "ui.error.phone.off": "the phone connection is off",
1221
- "ui.error.phone.off.hint": "it is turned on by strom phone on in a terminal, or in the Strom app (Research → Connect phone…)",
1221
+ "ui.error.phone.off.hint": "it is turned on by strom phone on in a terminal, or in the Strom app (Research → Other actions → Connect phone…)",
1222
1222
  "ui.error.phone.qr-terminal": "the QR code to pair a phone is shown only in a terminal or in the Strom app",
1223
- "ui.error.phone.qr-terminal.hint": "strom phone qr in a terminal, or Research → Connect phone… in the Strom app",
1223
+ "ui.error.phone.qr-terminal.hint": "strom phone qr in a terminal, or Research → Other actions → Connect phone… in the Strom app",
1224
1224
  "ui.error.phone.address": "the phone's server of this computer has no address {address}",
1225
1225
  "ui.error.phone.address.hint": "its addresses: {addresses}",
1226
1226
  "ui.phone.page.title": "Strom research",
@@ -1228,14 +1228,14 @@ export const UI = {
1228
1228
  "ui.phone.page.paired.title": "Paired",
1229
1229
  "ui.phone.page.paired": "This phone is paired with the research on the computer. It stays paired until it is disconnected on the computer.",
1230
1230
  "ui.phone.page.expired.title": "The code no longer holds",
1231
- "ui.phone.page.expired": "A QR code pairs one phone, within 10 minutes. A new one is shown by the Strom app on the computer (Research → Connect phone…).",
1231
+ "ui.phone.page.expired": "A QR code pairs one phone, within 10 minutes. A new one is shown by the Strom app on the computer (Research → Other actions → Connect phone…).",
1232
1232
  "ui.phone.page.unreachable.title": "The computer does not answer",
1233
1233
  "ui.phone.page.unreachable": "Is the computer on, the phone connection turned on, and the phone on the same Wi-Fi?",
1234
1234
  "ui.phone.page.retry": "Try again",
1235
1235
  "ui.phone.page.unpaired.title": "This phone is not paired with this computer",
1236
1236
  "ui.phone.page.unpaired.why": "The phone was unpaired on the computer, the computer’s address changed, or the page was opened without the QR code.",
1237
- "ui.phone.page.unpaired.how": "On the computer in the Strom app: Research → Connect phone, then scan the QR code.",
1238
- "ui.phone.page.unpaired.howHome": "The icon on the home screen cannot scan a QR code. The page is opened in the phone’s browser (Safari, Chrome) by the QR code scanned with the camera (on the computer in the Strom app: Research → Connect phone); from there the icon is added to the home screen again.",
1237
+ "ui.phone.page.unpaired.how": "On the computer in the Strom app: Research → Other actions → Connect phone, then scan the QR code.",
1238
+ "ui.phone.page.unpaired.howHome": "The icon on the home screen cannot scan a QR code. The page is opened in the phone’s browser (Safari, Chrome) by the QR code scanned with the camera (on the computer in the Strom app: Research → Other actions → Connect phone); from there the icon is added to the home screen again.",
1239
1239
  "ui.phone.page.unpaired.queue": "{n, plural, one {# unsent photo is waiting on this phone} other {# unsent photos are waiting on this phone}}",
1240
1240
  "ui.phone.page.unpaired.queueBody": "They are sent automatically after pairing with this computer again.",
1241
1241
  "ui.phone.page.ago.s": "{n} s ago",
@@ -1666,7 +1666,7 @@ export const UI = {
1666
1666
  "ui.move.cancel": "Cancel",
1667
1667
  "ui.move.cancelled": "Nothing was set up.",
1668
1668
  "ui.move.without": "Go on without a browser: the research (or a Strom archive) made from the file",
1669
- "ui.move.without.what": "The app in {from} does not connect to the research: nothing of the app goes live — neither a research nor a Strom archive. Changes from the app go to the research as a file (Export → GEDCOM in the app, strom sync <file> in the research), the research's version comes back by importing a file, and photos do not go that way.",
1669
+ "ui.move.without.what": "The app in {from} does not connect to the research: nothing of the app goes live — neither a research nor a Strom archive. Changes from the app go to the research as a file (Export… → GEDCOM in the app, strom sync <file> in the research), the research's version comes back by importing a file, and photos do not go that way.",
1670
1670
  "ui.move.without.later.here": "The full connection is only through Chrome, Edge, Brave or Firefox, and {browser} is here: strom app (the menu's Strom app) opens the research in it at any time, and the move is finished.",
1671
1671
  "ui.move.without.later.none": "The full connection is only through Chrome, Edge, Brave or Firefox: once one is installed, strom app (the menu's Strom app) opens the research there and the move is finished.",
1672
1672
  "ui.move.nobrowser": "This computer has no browser the Strom app connects to the research from. The tree can move to one that can — Google Chrome or Microsoft Edge, free to download.",
@@ -2167,6 +2167,8 @@ export const UI = {
2167
2167
  "ui.error.person.needs-reason.hint": "e.g. --reason \"misread: the register has Novák, not Nowak\"",
2168
2168
  "ui.error.retract.needs-reason": "retracting needs --reason",
2169
2169
  "ui.error.retract.needs-reason.hint": "e.g. --reason \"misread: the entry names Anna, not Anton\"",
2170
+ "ui.error.name.retracted": "the name {name} of {id} is retracted: {reason}",
2171
+ "ui.error.name.retracted.hint": "right after all: strom name restore {id} \"{name}\" --reason \"…\"",
2170
2172
  "ui.error.merge.needs-reason": "merging needs --reason",
2171
2173
  "ui.error.merge.needs-reason.hint": "--reason \"<why>\"",
2172
2174
  "ui.error.record.needs-reason": "changing {fields} of {id} needs --reason",
@@ -13,7 +13,7 @@ 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, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsSourceReads, appShowsFactStatus, appShowsStoryDrafts, appTurnsExcerpts } from "../core/stromapp.js";
16
+ import { appKnowsArchive, appKnowsNoCouple, appShowsHypothesisLinks, appShowsReadings, appShowsOmittedImages, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsSourceReads, appShowsFactStatus, appShowsStoryDrafts, appTurnsExcerpts } from "../core/stromapp.js";
17
17
  import { EXCERPT_QUALITIES, EXCERPT_SCOPES } from "../core/model.js";
18
18
  import { ui } from "../cli/ui.js";
19
19
  import { isAgent } from "../core/which.js";
@@ -93,7 +93,9 @@ export function writeTreeGed(tree, opts) {
93
93
  const conflictSides = opts.for === "strom" && appDecidesConflicts(new Settings(tree.env, {}), opts.stromVersion);
94
94
  // …and the reading of a document each source is an entry of, for an app that opens it
95
95
  const readings = opts.for === "strom" && appShowsReadings(new Settings(tree.env, {}), opts.stromVersion);
96
- const result = exportGedcom(tree, { for: opts.for, 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 } : {}) });
96
+ // …and why a source has no image though the research has one, for an app that says so and asks for it
97
+ const omitted = !!images && appShowsOmittedImages(new Settings(tree.env, {}), opts.stromVersion);
98
+ const result = exportGedcom(tree, { for: opts.for, 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 } : {}) });
97
99
  const findings = validateGedcom(result.text, opts.for === "standard" ? { strict: true } : {});
98
100
  const file = opts.out ?? gedFile(tree, opts.for, opts.research);
99
101
  if (!hasGedErrors(findings) && !tree.dryRun) {
@@ -411,6 +411,7 @@ register({
411
411
  throw new UsageError(`the name ${gedcomName(gone)} of ${p.id} was retracted: ${gone.retracted.reason}`, {
412
412
  hint: `right after all: strom name restore ${p.id} "${gedcomName(gone)}" --reason "…"`,
413
413
  code: "name.retracted",
414
+ params: { name: gedcomName(gone), id: p.id, reason: gone.retracted.reason },
414
415
  });
415
416
  const r = addName(tree, p.id, { name: args[1], kind: opts.kind, citation: citationOf(opts), primary: Boolean(opts.primary), prefix: opts.prefix, suffix: opts.suffix });
416
417
  return { text: written(tree), data: { person: r.person, name: r.name } };
@@ -111,13 +111,13 @@ register({
111
111
  description: "The phone's camera opens the page of the phone's server on this computer and pairs it (the same Wi-Fi). The code\n" +
112
112
  "holds 10 minutes, for one phone; shown again meanwhile, the same code. Only in the user's own terminal — never\n" +
113
113
  "with --json or without a terminal: the code is a phone's way into the research (the Strom app shows it too:\n" +
114
- "Research → Connect phone…). The page is in the research language.",
114
+ "Research → Other actions → Connect phone…). The page is in the research language.",
115
115
  options: [{ name: "address", type: "string", value: "<url>", description: "another address of this computer (strom phone says them), e.g. a cable's beside the Wi-Fi" }],
116
116
  examples: ["strom phone qr", "strom phone qr --address http://192.168.0.5:47800"],
117
117
  async run(ctx, { opts }) {
118
118
  const lang = ctx.uiLang();
119
119
  if (!showsQr(ctx))
120
- throw new StromError("the QR code to pair a phone is shown in the user's own terminal or in the Strom app, never to an agent", { hint: "the user runs strom phone qr in their terminal, or Research → Connect phone… in the Strom app", code: "phone.qr-terminal" });
120
+ throw new StromError("the QR code to pair a phone is shown in the user's own terminal or in the Strom app, never to an agent", { hint: "the user runs strom phone qr in their terminal, or Research → Other actions → Connect phone… in the Strom app", code: "phone.qr-terminal" });
121
121
  const s = await withAddresses(ctx);
122
122
  if (s.state !== "on" || !s.addresses.length)
123
123
  return { text: s.state === "on" || s.state === "error" ? stateLines(lang, s) : ui(lang, "ui.phone.qr.off"), data: { state: s.state } };
@@ -31,7 +31,7 @@ import { recordReader, tokensOf, usageOpt } from "../core/metrics.js";
31
31
  import { readerLimit } from "../core/readstats.js";
32
32
  import { guardIndexFirst, INDEX_FIRST_DEFAULT } from "../core/indexfirst.js";
33
33
  import { bookViewSizes, readingOf, treeTuning, tuningOn } from "../core/tune.js";
34
- import { claimStem, readerAgent, readerDesk, refuseReadersInSandbox } from "./readers.js";
34
+ import { claimStem, noReaderFor, readerAgent, readerDesk, refuseReadersInSandbox } from "./readers.js";
35
35
  import { noReaderHere, refusedBySandbox } from "../core/sandbox.js";
36
36
  import { activeFetches } from "../core/fetching.js";
37
37
  import { finishOf } from "../core/clock.js";
@@ -243,7 +243,7 @@ register({
243
243
  const agentId = readerAgent(ctx, tree);
244
244
  const runner = RUNNERS[agentId];
245
245
  if (!runner)
246
- throw new UsageError(`no reader for agent "${agentId}" yet`, { hint: "strom read works with Claude Code: strom read … --agent claude" });
246
+ throw noReaderFor(agentId, "strom read");
247
247
  if (agentId !== "script" && !which(runner.command, ctx.env))
248
248
  throw new StromError(`${runner.command} is not installed`);
249
249
  const model = typeof opts.model === "string" ? opts.model : ctx.settings.models(agentId, tree.config).vision;
@@ -440,7 +440,8 @@ register({
440
440
  const own = path.join(work, `${stem}-${k + 1}`);
441
441
  fs.mkdirSync(own, { recursive: true });
442
442
  const settingsFile = path.join(own, "reader-settings.json");
443
- fs.writeFileSync(settingsFile, JSON.stringify(readerSettings(group.flatMap((g) => [g.view, ...(g.parts ?? []).map((p) => p.view)]), report, permissionPath), null, 2));
443
+ const seen = group.flatMap((g) => [g.view, ...(g.parts ?? []).map((p) => p.view)]);
444
+ fs.writeFileSync(settingsFile, JSON.stringify(readerSettings(seen, report, permissionPath), null, 2));
444
445
  const context = typeof opts.context === "string" && opts.context.trim() ? `Context: ${opts.context.trim().replace(/\s*\n\s*/g, " ")}\n` : "";
445
446
  // a blind reading says so in its head: its "unclear" over a place the researcher looked at before is a check
446
447
  const head = `# Reading ${label} · batch ${k + 1}${fetching ? "" : ` of ${groups.length}`}\nQuestion: ${question.replace(/\s*\n\s*/g, " ")}\n${context}${opts.blind ? "Blind: yes\n" : ""}\n${NEXT_BLOCK}\n`;
@@ -459,6 +460,7 @@ register({
459
460
  env: { ...withoutAgentMarks(env), STROM_NONINTERACTIVE: "1", STROM_READER: "1" },
460
461
  settingsFile,
461
462
  reader: true,
463
+ readerFiles: { views: seen, report },
462
464
  // without the user's own add-ons (skills, plugins, MCP servers) unless they said otherwise
463
465
  ...(ctx.settings.agentAddons(tree.config) ? {} : { clean: true }),
464
466
  timeoutMs: minutes * 60_000,
@@ -76,6 +76,11 @@ export function claimStem(root, prefix, base, taken) {
76
76
  }
77
77
  return `${base}-${process.pid}`;
78
78
  }
79
+ /** An agent strom has no reader for: the agents that read said (each agent of a research does), none of them preferred. */
80
+ export function noReaderFor(agentId, command) {
81
+ const agents = Object.keys(RUNNERS).filter((a) => a !== "script");
82
+ return new UsageError(`no reader for agent "${agentId}"`, { hint: `${command} reads with ${agents.join(", ")}: ${command} … --agent <one of them>` });
83
+ }
79
84
  /** Inside a sandbox that lets no agent start (Codex's) no reader is started: said at once, with what to do instead. */
80
85
  export function refuseReadersInSandbox(ctx, command) {
81
86
  const sb = sandboxOf(ctx.env);
@@ -92,7 +97,7 @@ export function readers(ctx, tree, shared, kind, first, opts, reportsAt, own = {
92
97
  const agentId = readerAgent(ctx, tree);
93
98
  const runner = RUNNERS[agentId];
94
99
  if (!runner)
95
- throw new UsageError(`no reader for agent "${agentId}" yet`, { hint: `strom ${kind} works with Claude Code: strom ${kind} … --agent claude` });
100
+ throw noReaderFor(agentId, `strom ${kind}`);
96
101
  if (agentId !== "script" && !which(runner.command, ctx.env))
97
102
  throw new StromError(`${runner.command} is not installed`);
98
103
  const model = typeof opts.model === "string" ? opts.model : ctx.settings.models(agentId, tree.config).vision;
@@ -135,6 +140,7 @@ export function readers(ctx, tree, shared, kind, first, opts, reportsAt, own = {
135
140
  env: { ...withoutAgentMarks(env), STROM_NONINTERACTIVE: "1", STROM_READER: "1" },
136
141
  settingsFile,
137
142
  reader: true,
143
+ readerFiles: { views, report },
138
144
  // without the user's own add-ons (skills, plugins, MCP servers) unless they said otherwise
139
145
  ...(ctx.settings.agentAddons(tree.config) ? {} : { clean: true }),
140
146
  timeoutMs: limit.minutes * 60_000,
@@ -344,7 +344,7 @@ register({
344
344
  throw new UsageError("--only goes with --apply");
345
345
  return {
346
346
  text: lines(got ? ui(lang, "ui.sync.got") : undefined, planText(tree, plan, incoming, shownAs, lang, edits)),
347
- data: { ...(got ? { received: got } : {}), base: plan.base, head: plan.head ?? null, partial: plan.partial, changes: plan.changes, strangers: plan.identity.strangers },
347
+ data: { ...(got ? { received: got } : {}), base: plan.base, head: plan.head ?? null, partial: plan.partial, changes: plan.changes, strangers: plan.identity.strangers, ...(plan.mended ? { mended: plan.mended } : {}) },
348
348
  };
349
349
  }
350
350
  const only = numbers(opts.only, plan.changes.length);
@@ -155,7 +155,38 @@ export function factSaid(tree, owner, eventId, lang) {
155
155
  const what = [f.value, humanDate(f.date, lang), humanPlace(f.place, f.house, lang)].filter(Boolean).join(", ");
156
156
  return `${eventName(f.kind, lang, f.label)}${what ? `: ${what}` : ""}`;
157
157
  }
158
- /** Lines of what a commit saved, in the research language, each with what it is about; its subject when it logged no operation. */
158
+ /** How many sources a line of the history names at most (the Strom app opens each from it). */
159
+ export const LINE_SOURCES_MAX = 20;
160
+ /** A source named by its ID in a summary of any script (never \b, which knows only ASCII letters). */
161
+ const SOURCE_IN = /(?<![\p{L}\p{M}\p{N}])S\d+(?![\p{L}\p{M}\p{N}])/gu;
162
+ /**
163
+ * The sources an operation is about, by the IDs the research has them under now: those it changed (its targets) and,
164
+ * a citation, those it cites or cites no more (its summary names them), a fact added, those it cites now — a source
165
+ * merged into another as that one.
166
+ */
167
+ function sourcesOf(tree, o) {
168
+ const ids = o.targets.filter((t) => /^S\d+$/.test(t));
169
+ if (/\.cite$/.test(o.op))
170
+ ids.push(...(o.summary.match(SOURCE_IN) ?? []));
171
+ // a fact added: the sources it cites (as it stands now — what the app would open to check it)
172
+ if (o.op === "event.add") {
173
+ const owner = tree.get(o.targets[0] ?? "");
174
+ const fact = owner?.events.find((e) => e.id === o.targets[1]);
175
+ if (fact && !fact.retracted)
176
+ ids.push(...fact.citations.map((c) => c.source));
177
+ }
178
+ const now = ids.map((id) => {
179
+ let r = tree.get(id);
180
+ for (let hops = 0; r?.mergedInto && hops < 10; hops++)
181
+ r = tree.get(r.mergedInto) ?? r;
182
+ return r?.id ?? id;
183
+ });
184
+ return [...new Set(now)].slice(0, LINE_SOURCES_MAX);
185
+ }
186
+ /**
187
+ * Lines of what a commit saved, in the research language, each with what it is about and the sources it names (for
188
+ * the Strom app to open them: /log `sources`); its subject when it logged no operation.
189
+ */
159
190
  export function changeLines(tree, ops, subject, lang, was) {
160
191
  const get = (id) => (id ? tree.get(id) : undefined);
161
192
  const who = (id) => {
@@ -231,9 +262,18 @@ export function changeLines(tree, ops, subject, lang, was) {
231
262
  };
232
263
  const out = [];
233
264
  let kind = "other";
265
+ let cites = [];
234
266
  const say = (line) => {
235
- if (line && !out.some((l) => l.text === line))
236
- out.push({ text: line, kind });
267
+ if (!line)
268
+ return;
269
+ const had = out.find((l) => l.text === line);
270
+ if (!had)
271
+ out.push({ text: line, kind, ...(cites.length ? { sources: cites } : {}) });
272
+ // the same line of another operation: its sources too
273
+ else
274
+ for (const id of cites)
275
+ if (!had.sources?.includes(id) && (had.sources?.length ?? 0) < LINE_SOURCES_MAX)
276
+ had.sources = [...(had.sources ?? []), id];
237
277
  };
238
278
  // Images downloaded: one line for each book, whatever their order.
239
279
  const images = new Map();
@@ -241,6 +281,7 @@ export function changeLines(tree, ops, subject, lang, was) {
241
281
  if (QUIET.has(o.op))
242
282
  continue;
243
283
  kind = kindOf(o.op);
284
+ cites = sourcesOf(tree, o);
244
285
  const [first, second] = o.targets;
245
286
  if (o.op === "media.add") {
246
287
  // a part of an image fetched sharper is no new scan: said as a part of its image, apart from the new scans
@@ -376,9 +417,9 @@ export function changeLines(tree, ops, subject, lang, was) {
376
417
  const key = SAID[o.op];
377
418
  say(key ? phrase(lang, key, { who: who(first), name: name(first) }) : changed(o));
378
419
  }
379
- const lines = out.map(({ text: line, kind }) => {
420
+ const lines = out.map(({ text: line, kind, sources }) => {
380
421
  if (!line.startsWith("\u0000media:"))
381
- return { text: line, kind };
422
+ return { text: line, kind, ...(sources ? { sources } : {}) };
382
423
  const key = line.slice("\u0000media:".length);
383
424
  const part = key.startsWith("part:");
384
425
  const book = key.slice(key.indexOf(":") + 1);