strom-research 1.25.1 → 1.25.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.
@@ -1,24 +1,218 @@
1
1
  {
2
- "about": "Words that tell a street or a house number in the place of a fact (\"Lhota, Dlouhá ulice 12\"): the place is the municipality, the street and the number its house. Compared folded (no accents, lower case), word by word, in any script. words: a word of a street anywhere in a part; suffixes: a word ending so (German Hauptstraße, Dutch Kerkstraat); last: only as the last word of a part (\"Main St.\" — \"St. Martin\" is a saint); house: what stands before a house number (\"č. p. 12\", \"Nr. 3\") — counted only with a number after it; numbered: municipalities whose number after the name is a part of them (\"Praha 5\"), never a house — a number alone after any other municipality (\"Lhota 16\") is a house only when a person says so for the whole municipality.",
2
+ "about": "Words that tell a street or a house number in the place of a fact (\"Lhota, Dlouhá ulice 12\"): the place is the municipality, the street and the number its house. Compared folded (no accents, lower case), word by word, in any script. words: a word of a street anywhere in a part; suffixes: a word ending so (German Hauptstraße, Dutch Kerkstraat); last: only as the last word of a part (\"Main St.\" — \"St. Martin\" is a saint); house: what stands before a house number (\"č. p. 12\", \"Nr. 3\") — counted only with a number after it; numbered: municipalities whose number after the name is a part of them (\"Praha 5\"), never a house — a number alone after any other municipality (\"Lhota 16\") is a house only when a person says so for the whole municipality. prepositions: what stands before a municipality written in a sentence (\"v Lhotě v čp. 12\", \"in Lhotau Nr. 3\") — taken off the municipality before a house number, only in lower case where it begins it.",
3
3
  "words": {
4
- "cs": ["ulice", "ul.", "náměstí", "nám.", "třída", "tř.", "nábřeží", "nábř.", "alej"],
5
- "sk": ["ulica", "námestie", "nábrežie", "trieda"],
6
- "pl": ["ulica", "plac", "pl.", "aleja", "rynek"],
7
- "de": ["straße", "strasse", "str.", "gasse", "platz", "weg", "allee"],
8
- "hu": ["utca", "tér", "út"],
9
- "sl": ["trg", "ulica"],
10
- "en": ["street", "road", "rd.", "avenue", "ave.", "lane", "square", "boulevard", "blvd."],
11
- "fr": ["rue", "boulevard", "avenue"],
12
- "it": ["via", "viale", "piazza", "corso"],
13
- "es": ["calle", "plaza", "avenida"],
14
- "pt": ["rua", "praça"],
15
- "nl": ["straat", "plein", "laan"],
16
- "ru": ["улица", "ул.", "проспект", "переулок", "пер.", "площадь", "пл.", "бульвар", "шоссе", "набережная", "наб."],
17
- "uk": ["вулиця", "вул.", "провулок", "площа", "проспект", "бульвар"],
18
- "bg": ["улица", "ул.", "булевард", "площад"]
4
+ "cs": [
5
+ "ulice",
6
+ "ul.",
7
+ "náměstí",
8
+ "nám.",
9
+ "třída",
10
+ "tř.",
11
+ "nábřeží",
12
+ "nábř.",
13
+ "alej"
14
+ ],
15
+ "sk": [
16
+ "ulica",
17
+ "námestie",
18
+ "nábrežie",
19
+ "trieda"
20
+ ],
21
+ "pl": [
22
+ "ulica",
23
+ "plac",
24
+ "pl.",
25
+ "aleja",
26
+ "rynek"
27
+ ],
28
+ "de": [
29
+ "straße",
30
+ "strasse",
31
+ "str.",
32
+ "gasse",
33
+ "platz",
34
+ "weg",
35
+ "allee"
36
+ ],
37
+ "hu": [
38
+ "utca",
39
+ "tér",
40
+ "út"
41
+ ],
42
+ "sl": [
43
+ "trg",
44
+ "ulica"
45
+ ],
46
+ "en": [
47
+ "street",
48
+ "road",
49
+ "rd.",
50
+ "avenue",
51
+ "ave.",
52
+ "lane",
53
+ "square",
54
+ "boulevard",
55
+ "blvd."
56
+ ],
57
+ "fr": [
58
+ "rue",
59
+ "boulevard",
60
+ "avenue"
61
+ ],
62
+ "it": [
63
+ "via",
64
+ "viale",
65
+ "piazza",
66
+ "corso"
67
+ ],
68
+ "es": [
69
+ "calle",
70
+ "plaza",
71
+ "avenida"
72
+ ],
73
+ "pt": [
74
+ "rua",
75
+ "praça"
76
+ ],
77
+ "nl": [
78
+ "straat",
79
+ "plein",
80
+ "laan"
81
+ ],
82
+ "ru": [
83
+ "улица",
84
+ "ул.",
85
+ "проспект",
86
+ "переулок",
87
+ "пер.",
88
+ "площадь",
89
+ "пл.",
90
+ "бульвар",
91
+ "шоссе",
92
+ "набережная",
93
+ "наб."
94
+ ],
95
+ "uk": [
96
+ "вулиця",
97
+ "вул.",
98
+ "провулок",
99
+ "площа",
100
+ "проспект",
101
+ "бульвар"
102
+ ],
103
+ "bg": [
104
+ "улица",
105
+ "ул.",
106
+ "булевард",
107
+ "площад"
108
+ ]
19
109
  },
20
- "suffixes": ["straße", "strasse", "str.", "gasse", "platz", "weg", "allee", "straat", "plein", "gracht", "gatan", "gade", "vej", "vägen"],
21
- "last": ["st.", "st"],
22
- "house": ["č. p.", "č.p.", "čp.", "č.", "čís.", "č. d.", "nr.", "nr", "no.", "nro.", "num.", "hnr.", "hausnr.", "haus", "house", "№", "д.", "дом"],
23
- "numbered": ["Praha", "Prag", "Brno", "Brünn", "Plzeň", "Pilsen", "Wien", "Budapest", "Paris", "Lyon", "Marseille"]
110
+ "suffixes": [
111
+ "straße",
112
+ "strasse",
113
+ "str.",
114
+ "gasse",
115
+ "platz",
116
+ "weg",
117
+ "allee",
118
+ "straat",
119
+ "plein",
120
+ "gracht",
121
+ "gatan",
122
+ "gade",
123
+ "vej",
124
+ "vägen"
125
+ ],
126
+ "last": [
127
+ "st.",
128
+ "st"
129
+ ],
130
+ "house": [
131
+ "č. p.",
132
+ "č.p.",
133
+ "čp.",
134
+ "č.",
135
+ "čís.",
136
+ "č. d.",
137
+ "nr.",
138
+ "nr",
139
+ "nr domu",
140
+ "nr. domu",
141
+ "numer domu",
142
+ "numer",
143
+ "dom nr",
144
+ "dom nr.",
145
+ "no.",
146
+ "nro.",
147
+ "num.",
148
+ "hnr.",
149
+ "hausnr.",
150
+ "haus",
151
+ "house",
152
+ "№",
153
+ "д.",
154
+ "дом"
155
+ ],
156
+ "numbered": [
157
+ "Praha",
158
+ "Prag",
159
+ "Brno",
160
+ "Brünn",
161
+ "Plzeň",
162
+ "Pilsen",
163
+ "Wien",
164
+ "Budapest",
165
+ "Paris",
166
+ "Lyon",
167
+ "Marseille"
168
+ ],
169
+ "prepositions": {
170
+ "cs": [
171
+ "v",
172
+ "ve",
173
+ "na",
174
+ "u",
175
+ "z",
176
+ "ze",
177
+ "do"
178
+ ],
179
+ "sk": [
180
+ "v",
181
+ "vo",
182
+ "na",
183
+ "u",
184
+ "z",
185
+ "zo",
186
+ "do"
187
+ ],
188
+ "pl": [
189
+ "w",
190
+ "we",
191
+ "na",
192
+ "u",
193
+ "z",
194
+ "ze",
195
+ "do"
196
+ ],
197
+ "de": [
198
+ "in",
199
+ "im",
200
+ "bei",
201
+ "zu",
202
+ "am",
203
+ "auf"
204
+ ],
205
+ "en": [
206
+ "in",
207
+ "at",
208
+ "of"
209
+ ],
210
+ "hu": [],
211
+ "sl": [
212
+ "v",
213
+ "na",
214
+ "pri",
215
+ "iz"
216
+ ]
217
+ }
24
218
  }
@@ -5,7 +5,7 @@
5
5
  import { langName } from "../core/lang.js";
6
6
  import { match } from "../cli/registry.js";
7
7
  import { synopsis } from "./sheet.js";
8
- import { factForms, personsHand, PLACES_IDENTIFY_ORIGIN, queueBatches, readPlacesFile, recordIndex } from "../core/placeid.js";
8
+ import { factForms, otherNames, personsHand, PLACES_IDENTIFY_ORIGIN, queueBatches, readPlacesFile, recordIndex } from "../core/placeid.js";
9
9
  import { historyOn } from "../core/pastevents.js";
10
10
  import { othersRecords } from "../core/placeadopt.js";
11
11
  import { foldText } from "../core/text.js";
@@ -24,6 +24,24 @@ function span(ys) {
24
24
  const b = Math.max(...ys);
25
25
  return a === b ? String(a) : `${a}–${b}`;
26
26
  }
27
+ /** A name as the agent writes it: name[@lang][:from-to]. */
28
+ function nameSaid(n) {
29
+ const years = n.from === undefined && n.to === undefined ? "" : n.from === n.to ? `:${n.from}` : `:${n.from ?? ""}-${n.to ?? ""}`;
30
+ return `${n.name}${n.lang ? `@${n.lang}` : ""}${years}`;
31
+ }
32
+ /** A record's names: today's, its other names (another language, old names — with their language and years), the forms of the facts. */
33
+ function recordNames(p) {
34
+ const rest = otherNames(p);
35
+ const alt = rest.filter((n) => n.kind !== "form");
36
+ const forms = rest.filter((n) => n.kind === "form");
37
+ return [
38
+ nameSaid(p.names[0]),
39
+ alt.length ? `other names ${alt.map(nameSaid).join(", ")}` : "",
40
+ forms.length ? `forms ${forms.map((n) => n.name).join(", ")}` : "",
41
+ ]
42
+ .filter(Boolean)
43
+ .join("; ");
44
+ }
27
45
  /** One place of the batch: its form, the facts, the people, the archives of its sources, the record of it. */
28
46
  function placeLine(tree, form, f, entry, p, others) {
29
47
  const areas = f
@@ -40,7 +58,7 @@ function placeLine(tree, form, f, entry, p, others) {
40
58
  f ? `${f.facts} fact${f.facts === 1 ? "" : "s"} ${span(f.years)}` : "",
41
59
  persons.length ? `people ${persons.join(" ")}${f.persons.size > persons.length ? " …" : ""}` : "",
42
60
  areas.length ? `sources from ${areas.join(", ")}` : "",
43
- p ? `record ${p.id}: ${p.names.map((n) => n.name).join(", ")}` : "no record yet",
61
+ p ? `record ${p.id}: ${recordNames(p)}` : "no record yet",
44
62
  p?.parent ? `in ${p.parent}` : "",
45
63
  p?.jurisdictions.length ? p.jurisdictions.map((j) => `${j.kind} ${j.name}${j.from || j.to ? ` ${j.from ?? "…"}–${j.to ?? "…"}` : ""}`).join(", ") : "",
46
64
  p?.coords ? `on the map ${p.coords.lat.toFixed(4)}, ${p.coords.lon.toFixed(4)}${personsHand(p) ? " (the person's — never moved)" : ""}` : "",
@@ -82,17 +100,32 @@ export function placesIdentifySections(tree, task, alone, settings) {
82
100
  pointer: "strom help place identify",
83
101
  text: [
84
102
  "## How — from the cheap steps up, the web last",
85
- "1. free, the tree first: the sources of the facts there (their archive, parish, register — strom source show S…), the other places of the same people and years (strom person card P…), the place records (strom place show L…), the records of other research on this computer (above); a place the tree tells apart already needs nothing else",
103
+ "1. free, the tree first: the sources of the facts there (their archive, parish, register — strom show S… or B…, any record by its ID), the other places of the same people and years (strom person card P…), the place records (strom place show L…), the records of other research on this computer (above); a place the tree tells apart already needs nothing else",
86
104
  "2. free, the writing: abbreviations and other spellings of one place (strom place rewrite … --to L…); a street or a house number written as a place (\"Lhota, Dlouhá\", \"Horní 12\"): the place is the municipality, the street goes into the house (--house), a house written there is never replaced",
87
105
  "3. the web last, for what is still open: gazetteers, the registers of municipalities, historical maps and the Wikipedia in the region's language. Only the place's name with its parish or district goes into a search — never a person's name, a date or anything else of the tree",
88
106
  "",
107
+ "",
108
+ "The record of a place:",
109
+ "- its first name is the official name of today in the language of the country it lies in now (a village now in Poland: Polish; the German name is historical) — the names of the facts and the old names go in as --form and --alt name@lang:from-to (an old name: from-to its years; one year: name@lang:1844)",
110
+ "- the record's other names above are known already (name@language:years): today's name in another language may be among them — look it up by that name before calling it not found, and set it first with --name when the reference confirms it",
111
+ "- a name of the record found wrong (another place's name, a slip of a record no fact writes any more): strom place names L… --drop \"<name>\" --why \"what shows it is wrong\" — never left in with a note that it is wrong",
112
+ "- what is found of a place goes into its record, also where the coordinates are right already: strom place verify L… --ok --why \"…\" --alt … --form … (never only into the history)",
113
+ "- a jurisdiction found wrong (another parish): strom place verify L… --ok --why \"what the records say\" --drop-jurisdiction parish:<name> --jurisdiction parish:<right one>:from-to (one year: parish:<name>:1899)",
114
+ "- the reference a check stood on: strom place verify L… --ok --why \"…\" --source-title \"…\" --source-url …",
115
+ "- a form of the facts that is a place with a record already (\"Nové Lhoty\" of Lhoty): strom place identify L… --form \"<form>\" — never a second record; two records of one place: strom place merge L… --into L… --reason \"…\"",
116
+ "",
89
117
  "Three degrees, never guessed:",
90
118
  "- sure (the parish of the records, the district and the years agree; no other place of the name fits): strom place identify \"<form>\" --lat … --lon … --why \"on what\" [--name, --form for each other writing of the facts, --alt for another language or an old name (name@lang:from-to), --jurisdiction kind:name:from-to, --source-title/--source-url of the reference read]",
91
119
  "- probable (1–3 places fit): strom place propose \"<form>\" --candidate \"name|lat|lon|why|district|country|title|url\" for each — the person chooses",
92
120
  "- not to be decided: strom place unlocated \"<form>\" --why \"…\" --needs \"what would decide it\" — it comes back by itself when that is known; a wrong place is worse than none",
93
121
  "- a place on the map the person put there is never moved: the records agree — strom place verify L… --ok --why \"…\"; they do not — strom place propose L… --conflict --candidate …; it cannot be told — strom place verify L… --undecided \"why\"",
94
122
  "- one place written several ways in the facts: strom place rewrite \"<form>\" --to L… (a street: --house \"<street>\"; a house written there is kept) when sure; else --propose --why \"…\" — the person says yes",
95
- ...(history ? ["- the history of the places is on: each place identified gets its regions too — strom history map \"<form>\" --region <id>:<from>-<to> (strom help history map)"] : []),
123
+ ...(history
124
+ ? [
125
+ "- the history of the places is on: each place identified gets its regions too — strom history map \"<form>\" --region <id>:<from>-<to> [--region …] (each region's ID as strom history status lists it; every period of the place covered, none left out)",
126
+ ]
127
+ : []),
128
+ "- this session is on the places only: no searches of records are written here (strom search add), nothing of the people",
96
129
  ].join("\n"),
97
130
  },
98
131
  ];
@@ -102,6 +135,8 @@ export function placesIdentifySections(tree, task, alone, settings) {
102
135
  cmd(["place", "unlocated"]),
103
136
  cmd(["place", "verify"]),
104
137
  cmd(["place", "rewrite"]),
138
+ cmd(["place", "merge"]),
139
+ cmd(["place", "names"]),
105
140
  cmd(["place", "show"]),
106
141
  ...(history ? [cmd(["history", "map"])] : []),
107
142
  ].filter(Boolean);
@@ -114,7 +149,7 @@ export function placesIdentifySections(tree, task, alone, settings) {
114
149
  pointer: "",
115
150
  text: [
116
151
  "## Finishing",
117
- `- every place above identified, proposed or not located: strom session close --done "how many identified, proposed, not located"${more ? ' --next "the next places of the queue"' : ""}`,
152
+ `- every place above identified, proposed or not located: strom session close --done "what was found of the places, in a few words" (strom puts the counts before it: identified, verified, asked, not located)${more ? ' --next "the next places of the queue"' : ""}`,
118
153
  `- time up before: strom session close --continue --summary "…" --next "the places left"`,
119
154
  alone
120
155
  ? `- the summary: plain words for the record (the menu, the Strom app), in ${langName(tree.lang)}, never addressed to the user, no advice; after strom session close you are done: a last message of one line at most`
package/dist/cli/ui.js CHANGED
@@ -2544,6 +2544,7 @@ export const UI = {
2544
2544
  "ui.conflict.titleAfter": "title after the name",
2545
2545
  "ui.conflict.parents": "parents",
2546
2546
  "ui.conflict.removed": "in the tree",
2547
+ "ui.conflict.title": "{who}: {what} — {ours} × {theirs}",
2547
2548
  "ui.conflict.removed.ours": "removed in the Strom app ({day}, {sync})",
2548
2549
  "ui.conflict.removed.theirs": "edited in another copy of the app",
2549
2550
  "ui.conflict.removing.ours": "edited later in another copy of the app",
@@ -230,7 +230,17 @@ register({
230
230
  ...shown.map((t) => [` ${t.state === "doing" ? ui(lang, "ui.plan.now") : `${++n}.`}`, `${truncate(humanTask(tree, t.what, lang), 110)}${lacksImages(tree, t) ? ui(lang, "ui.plan.images") : ""}${offLines.has(t.id) ? ui(lang, "ui.plan.offline") : ""}`]),
231
231
  ...later.slice(0, 5).map(({ t, a }) => [` ${ui(lang, "ui.plan.later")}`, `${truncate(humanTask(tree, t.what, lang), 110)}${ui(lang, "ui.plan.asleep", { at: clockUp(a.until), host: a.host })}`]),
232
232
  ];
233
- const storyNames = [...new Set(stories.map((t) => tree.get(t.subject[0] ?? "")).filter((p) => !!p).map((p) => `${displayName(p)} [${p.id}]`))];
233
+ // whose story: a person's, or a couple's (strom story set F…: the partners' names)
234
+ const whose = (id) => {
235
+ const rec = tree.get(id);
236
+ if (rec?.type === "person")
237
+ return `${displayName(rec)} [${rec.id}]`;
238
+ if (rec?.type !== "family")
239
+ return undefined;
240
+ const names = rec.partners.map((x) => tree.get(x)).filter((p) => p?.type === "person").map((p) => displayName(p));
241
+ return `${names.join(" & ") || rec.id} [${rec.id}]`;
242
+ };
243
+ const storyNames = [...new Set(stories.map((t) => whose(t.subject[0] ?? "")).filter((x) => !!x))];
234
244
  const out = [ui(lang, "ui.plan.title", { name: tree.config.name })];
235
245
  if (!queue.length && !later.length)
236
246
  out.push(` ${ui(lang, "ui.plan.none")}`);
@@ -26,7 +26,7 @@ import { displayName } from "../core/people.js";
26
26
  import { historyOn, readLocal } from "../core/pastevents.js";
27
27
  import { placeRecord } from "../core/placehistory.js";
28
28
  import { foldText } from "../core/text.js";
29
- import { recordIndex, unlocatePlace } from "../core/placeid.js";
29
+ import { addOtherNames, recordIndex, unlocatePlace } from "../core/placeid.js";
30
30
  function shared(ctx) {
31
31
  const s = ctx.settings.shared()?.value;
32
32
  if (!s)
@@ -237,7 +237,7 @@ register({
237
237
  args: [{ name: "place", description: "the place, as the facts write it", required: true }],
238
238
  options: [
239
239
  { name: "region", type: "string", multiple: true, value: "<id>[:<from>-<to>]", description: "a region it belonged to, with the years (repeatable)" },
240
- { name: "also", type: "string", multiple: true, value: "<name>", description: "another name of the place (another language, a historical name; repeatable)" },
240
+ { name: "also", type: "string", multiple: true, value: "<name[@lang][:from-to]>", description: "another name of the place (another language, a historical name; kept in its place record too; repeatable)" },
241
241
  { name: "unlocated", type: "boolean", description: "the place cannot be located" },
242
242
  { name: "why", type: "string", value: "<text>", description: "why it cannot be located" },
243
243
  ...textOpts("name", "the name of a new region"),
@@ -274,6 +274,8 @@ register({
274
274
  ensureRegion(sh, missing[0], name);
275
275
  }
276
276
  const m = mapPlace(tree, key, { regions: links, also: listOpt(opts.also) });
277
+ // the names are the place's: its record keeps them too (spec 034 — the app and the identification read them there)
278
+ addOtherNames(tree, key, listOpt(opts.also));
277
279
  return { text: lines(...tree.written.map((o) => o.summary), missing.length && tree.dryRun ? `would make the region ${missing[0]}` : undefined), data: { place: key, map: m } };
278
280
  },
279
281
  }, {
@@ -8,9 +8,8 @@ import { UsageError } from "../core/errors.js";
8
8
  import { listOpt } from "../core/records.js";
9
9
  import { refuseInArchive } from "../core/state.js";
10
10
  import { isAgent } from "../core/which.js";
11
- import { answerPlaces, cancelPlaces, factForms, identifyEstimate, identifyPlace, parseCandidate, parseJurisdiction, PLACE_TAKES, placeState, proposePlace, queueBatches, queueOrder, queuePlaces, raisePlacesTask, readPlacesFile, recordIndex, rewriteForm, undoPlaces, unlocatePlace, verifyPlace, identifyingNow, placeIdOf, PLACE_FLAGS, REQUEST_MAX, } from "../core/placeid.js";
11
+ import { answerPlaces, cancelPlaces, dropPlaceNames, factForms, identifyEstimate, identifyPlace, parseCandidate, parseJurisdiction, PLACE_TAKES, placeState, proposePlace, queueBatches, queueOrder, queuePlaces, raisePlacesTask, readPlacesFile, recordIndex, rewriteForm, undoPlaces, unlocatePlace, verifyPlace, identifyingNow, mergePlaces, parseAltName, parseDrop, placeIdOf, PLACE_FLAGS, REQUEST_MAX, } from "../core/placeid.js";
12
12
  import { actsAsArchive } from "../core/state.js";
13
- import { altName } from "./sources.js";
14
13
  function str(v) {
15
14
  return typeof v === "string" && v.trim() ? v.trim() : undefined;
16
15
  }
@@ -18,6 +17,16 @@ function written(tree) {
18
17
  return lines(...tree.written.map((o) => o.summary));
19
18
  }
20
19
  const name = (p) => p.names[0]?.name ?? p.id;
20
+ /** What identify and verify write of a place beside its coordinates. */
21
+ const DETAIL_OPTIONS = [
22
+ { name: "name", type: "string", value: "<name>", description: "today's (official) name, when the record's first is not it: the old one stays as a form or an old name" },
23
+ { name: "alt", type: "string", multiple: true, value: "<name[@lang][:from-to]>", description: 'another name of the place (another language, an old name, an old spelling), e.g. "Ober Lhota@de:-1945"; one year: "Lhotow@cs:1654"; a --form of the same name is kept as this name alone (repeatable)' },
24
+ { name: "form", type: "string", multiple: true, value: "<place>", description: "another form of the facts that is the same place (kept among the record's names as a form, out of the queue; repeatable)" },
25
+ { name: "kind", type: "string", value: "<kind>", description: "village, town, part of a municipality, …" },
26
+ { name: "parent", type: "string", value: "<L…>", description: "the place it is part of" },
27
+ { name: "jurisdiction", type: "string", multiple: true, value: "<kind:name[:from-to]>", description: 'e.g. "parish:Lhotov:1784-", "district:Lhotov" or one year "district:Lhotov:1899" (repeatable)' },
28
+ { name: "drop-jurisdiction", type: "string", multiple: true, value: "<kind:name>", description: "a jurisdiction of the record found wrong: taken off, --why says why (repeatable)" },
29
+ ];
21
30
  register({
22
31
  path: ["place", "identify"],
23
32
  summary: "Write a place identified surely: its coordinates, names, parent and jurisdictions, why and from what",
@@ -32,15 +41,12 @@ register({
32
41
  { name: "why", type: "string", value: "<text>", description: "why it is this place (the records' parish, district, years)" },
33
42
  { name: "source-title", type: "string", value: "<title>", description: "the reference it stands on (a gazetteer, a register of places)" },
34
43
  { name: "source-url", type: "string", value: "<url>", description: "its address" },
35
- { name: "name", type: "string", value: "<name>", description: "today's name, when the record's first is not it" },
36
- { name: "alt", type: "string", multiple: true, value: "<name[@lang][:from-to]>", description: 'another name of the place (another language, an old name), e.g. "Ober Lhota@de:-1945"; a --form of the same name is kept as this name alone (repeatable)' },
37
- { name: "form", type: "string", multiple: true, value: "<place>", description: "another form of the facts that is the same place (kept among the record's names as a form; repeatable)" },
38
- { name: "kind", type: "string", value: "<kind>", description: "village, town, part of a municipality, …" },
39
- { name: "parent", type: "string", value: "<L…>", description: "the place it is part of" },
40
- { name: "jurisdiction", type: "string", multiple: true, value: "<kind:name[:from-to]>", description: 'e.g. "parish:Lhotov:1784-" or "district:Lhotov" (repeatable)' },
44
+ ...DETAIL_OPTIONS,
45
+ { name: "separate", type: "string", multiple: true, value: "<L…>", description: "a record nearer than 1.5 km that is another place (a part of it, a neighbour) — said, never assumed (repeatable)" },
41
46
  ],
42
47
  examples: [
43
48
  'strom place identify "H. Lhota" --lat 49.61 --lon 15.23 --name "Horní Lhota" --jurisdiction "parish:Lhotov" --why "the parish of the registers; the other Horní Lhota is 200 km away" --source-title "Register of places"',
49
+ 'strom place identify "Lhota" --lat 50.2 --lon 15.9 --name "Lhotów" --alt "Lhotau@de:-1945" --why "…"',
44
50
  ],
45
51
  run(ctx, { args, opts }) {
46
52
  const tree = ctx.tree();
@@ -51,21 +57,17 @@ register({
51
57
  const url = str(opts["source-url"]);
52
58
  if (url && !title)
53
59
  throw new UsageError("--source-url with its --source-title");
54
- const p = identifyPlace(tree, args[0], {
60
+ const r = identifyPlace(tree, args[0], {
55
61
  lat,
56
62
  lon,
57
63
  why: String(opts.why ?? ""),
58
64
  ...(title ? { source: { title, ...(url ? { url } : {}) } } : {}),
59
- ...(str(opts.name) ? { name: str(opts.name) } : {}),
60
- alt: listOpt(opts.alt).map(altName),
61
- forms: listOpt(opts.form),
62
- ...(str(opts.kind) ? { kind: str(opts.kind) } : {}),
63
- ...(str(opts.parent) ? { parent: placeRef(String(opts.parent)) } : {}),
64
- jurisdictions: listOpt(opts.jurisdiction).map(parseJurisdiction),
65
+ ...detailsOf(opts),
66
+ separate: listOpt(opts.separate),
65
67
  by: isAgent(ctx.env) || ctx.env.STROM_SESSION ? "research" : "person",
66
68
  });
67
69
  raisePlacesTask(tree, actsAsArchive(tree));
68
- return { text: written(tree), data: { place: p } };
70
+ return stepResult(tree, r);
69
71
  },
70
72
  }, {
71
73
  path: ["place", "unlocated"],
@@ -114,19 +116,53 @@ register({
114
116
  group: "sources",
115
117
  tree: true,
116
118
  writes: true,
119
+ description: "The coordinates are never moved; what the check found of the place is written with it: its other and old names (--alt), the forms of the facts that are it (--form: no second record of one place), today's name (--name), its jurisdictions (--jurisdiction; a wrong one taken off with --drop-jurisdiction and --why).",
117
120
  options: [
118
121
  { name: "ok", type: "boolean", description: "the records agree with the coordinates" },
119
122
  { name: "undecided", type: "string", value: "<why>", description: "it cannot be decided, and why" },
120
- { name: "why", type: "string", value: "<text>", description: "with --ok: on what" },
123
+ { name: "why", type: "string", value: "<text>", description: "with --ok: on what (kept with the check); with --drop-jurisdiction: why it is wrong" },
124
+ { name: "source-title", type: "string", value: "<title>", description: "the reference the check stands on (a gazetteer, a register of places)" },
125
+ { name: "source-url", type: "string", value: "<url>", description: "its address" },
126
+ ...DETAIL_OPTIONS,
121
127
  ],
122
128
  args: [{ name: "place", description: "place ID (L0012) or the place as the facts write it", required: true }],
123
- examples: ['strom place verify L0002 --ok', 'strom place verify L0002 --undecided "the records name no parish"'],
129
+ examples: [
130
+ 'strom place verify L0002 --ok --why "the parish of the registers"',
131
+ 'strom place verify L0002 --ok --alt "Lhotau@de:-1945" --form "Nová Lhota" --drop-jurisdiction "parish:Lhotov" --jurisdiction "parish:Horní Lhota" --why "the town had its own parish; Lhotov was the source\'s"',
132
+ 'strom place verify L0002 --undecided "the records name no parish"',
133
+ ],
124
134
  run(ctx, { args, opts }) {
125
135
  const tree = ctx.tree();
126
136
  refuseInArchive(tree, "place verify", ctx.settings.agent(tree.config).value);
127
137
  if (!!opts.ok === !!opts.undecided)
128
- throw new UsageError("--ok or --undecided \"<why>\"");
129
- const p = verifyPlace(tree, args[0], opts.ok ? { ok: true, ...(str(opts.why) ? { why: str(opts.why) } : {}) } : { ok: false, why: String(opts.undecided) });
138
+ throw new UsageError("--ok or --undecided \"<why>\"", { hint: `strom place verify ${args[0] ?? "L…"} --ok --why "on what" (names found: --alt, --form)` });
139
+ const title = str(opts["source-title"]);
140
+ const url = str(opts["source-url"]);
141
+ if (url && !title)
142
+ throw new UsageError("--source-url with its --source-title");
143
+ const source = title ? { source: { title, ...(url ? { url } : {}) } } : {};
144
+ const r = verifyPlace(tree, args[0], opts.ok ? { ok: true, ...(str(opts.why) ? { why: str(opts.why) } : {}), ...source } : { ok: false, why: String(opts.undecided), ...source }, detailsOf(opts));
145
+ raisePlacesTask(tree, actsAsArchive(tree));
146
+ return stepResult(tree, r);
147
+ },
148
+ }, {
149
+ path: ["place", "merge"],
150
+ summary: "Two records of one place made one: names, forms and jurisdictions into the other, the duplicate retracted",
151
+ group: "sources",
152
+ tree: true,
153
+ writes: true,
154
+ description: "Only when sure it is one place (the same parish and district, an old name of the town); --reason says why. The person's coordinates win. Records further than 1.5 km apart are not merged: ask the person with strom place propose --conflict.",
155
+ args: [{ name: "place", description: "the duplicate's ID (L0012)", required: true }],
156
+ options: [
157
+ { name: "into", type: "string", value: "<L…>", description: "the record it is the same place as" },
158
+ ],
159
+ examples: ['strom place merge L0003 --into L0002 --reason "Nová Lhota is the old name of Horní Lhota; the same parish"'],
160
+ run(ctx, { args, opts }) {
161
+ const tree = ctx.tree();
162
+ refuseInArchive(tree, "place merge", ctx.settings.agent(tree.config).value);
163
+ if (!str(opts.into))
164
+ throw new UsageError("--into <L…>: the record it is the same place as", { hint: `strom place merge ${args[0]} --into L… --reason "…"` });
165
+ const p = mergePlaces(tree, args[0], String(opts.into), String(opts.reason ?? ""));
130
166
  raisePlacesTask(tree, actsAsArchive(tree));
131
167
  return { text: written(tree), data: { place: p } };
132
168
  },
@@ -156,11 +192,35 @@ register({
156
192
  propose: !!opts.propose,
157
193
  ...(str(opts.why) ? { why: str(opts.why) } : {}),
158
194
  });
195
+ raisePlacesTask(tree, actsAsArchive(tree));
159
196
  return {
160
197
  text: lines(written(tree), r.left ? `left: ${counted(r.left, "fact")} (their house says another thing — never written over)` : undefined),
161
198
  data: { place: r.place, changed: r.changed, left: r.left, asked: r.asked },
162
199
  };
163
200
  },
201
+ }, {
202
+ path: ["place", "names"],
203
+ summary: "Take a wrong name off a place record (a slip of a record kept as a form, another place's name), with the reason",
204
+ group: "sources",
205
+ tree: true,
206
+ writes: true,
207
+ description: "Never today's name (the first: --name of strom place verify or identify sets another) and never a name a fact still writes for the place (strom place rewrite first). Other names are added with --alt and --form of strom place identify or verify.",
208
+ args: [{ name: "place", description: "place ID (L0012)", required: true }],
209
+ options: [
210
+ { name: "drop", type: "string", multiple: true, value: "<name>", description: "the name to take off, as the record has it" },
211
+ { name: "why", type: "string", value: "<text>", description: "why it is wrong (kept with the step)" },
212
+ ],
213
+ examples: ['strom place names L0002 --drop "v Lhotě v" --why "a slip of the register; the facts write Lhota now"'],
214
+ run(ctx, { args, opts }) {
215
+ const tree = ctx.tree();
216
+ refuseInArchive(tree, "place names", ctx.settings.agent(tree.config).value);
217
+ const r = dropPlaceNames(tree, args[0], listOpt(opts.drop), String(opts.why ?? ""));
218
+ raisePlacesTask(tree, actsAsArchive(tree));
219
+ return {
220
+ text: lines(`${r.place.id}: ${r.dropped.map((n) => n.name).join(", ")} taken off`, `undo (the person's): strom place undo ${r.undo}`, written(tree)),
221
+ data: { place: r.place, dropped: r.dropped, undo: r.undo },
222
+ };
223
+ },
164
224
  }, {
165
225
  path: ["place", "queue"],
166
226
  summary: "Queue places to be identified (or their coordinates checked) — or show the queue",
@@ -275,12 +335,12 @@ register({
275
335
  },
276
336
  }, {
277
337
  path: ["place", "undo"],
278
- summary: "Undo the person's last step on places (answers)",
338
+ summary: "Undo a step on places by its id (the person's answers, names taken off, the queue tidied)",
279
339
  group: "sources",
280
340
  tree: true,
281
341
  writes: true,
282
- person: "Undoing the person's answers is the person's own",
283
- args: [{ name: "step", description: "the step's id (the answer prints it)", required: true }],
342
+ person: "Undoing a step on places is the person's own",
343
+ args: [{ name: "step", description: "the step's id (the step prints it; each of the last 20 steps once)", required: true }],
284
344
  examples: ["strom place undo u1a2b3c"],
285
345
  run(ctx, { args }) {
286
346
  const tree = ctx.tree();
@@ -380,6 +440,23 @@ const SKIP_SAID = {
380
440
  unknown: "no fact of the tree has it",
381
441
  unlocated: "not located",
382
442
  };
443
+ function detailsOf(opts) {
444
+ return {
445
+ ...(str(opts.name) ? { name: str(opts.name) } : {}),
446
+ alt: listOpt(opts.alt).map(parseAltName),
447
+ forms: listOpt(opts.form),
448
+ ...(str(opts.kind) ? { kind: str(opts.kind) } : {}),
449
+ ...(str(opts.parent) ? { parent: placeRef(String(opts.parent)) } : {}),
450
+ jurisdictions: listOpt(opts.jurisdiction).map(parseJurisdiction),
451
+ drop: listOpt(opts["drop-jurisdiction"]).map(parseDrop),
452
+ };
453
+ }
454
+ function stepResult(tree, r) {
455
+ return {
456
+ text: lines(written(tree), r.today ? `today's name first: ${r.today} (the one other name in use since a year with no end; --name sets another)` : undefined),
457
+ data: { place: r.place, ...(r.today ? { today: r.today } : {}), ...(r.dropped.length ? { dropped: r.dropped } : {}) },
458
+ };
459
+ }
383
460
  function placeRef(ref) {
384
461
  const m = /^[Ll](\d+)$/u.exec(ref.trim());
385
462
  if (!m)
@@ -62,6 +62,7 @@ import { isArchive } from "../core/mode.js";
62
62
  import { afterDeathProposals, settleAfterDeath } from "../core/afterdeath.js";
63
63
  import { usageOpt, webOpt } from "../core/metrics.js";
64
64
  import { calibrationKey, viewModel } from "../core/viewsizes.js";
65
+ import { claudeConfigSet, claudeLoggedIn, claudeLoginCommand } from "../core/claudedir.js";
65
66
  function written(tree) {
66
67
  return lines(...tree.written.map((o) => o.summary));
67
68
  }
@@ -874,7 +875,14 @@ register(updatedDuringRun({
874
875
  let gateAnswered = false;
875
876
  if (gate)
876
877
  out(ui(lang, gateHolds ? "ui.run.gate" : "ui.run.gate.once", { name: gate.manifest.title ?? gate.name }));
877
- for (let i = 0; i < max; i++) {
878
+ // Claude Code of a folder said outright (CLAUDE_CONFIG_DIR, agent.claudeConfigDir) not logged in there: no session
879
+ // would work — said before the first one, with how to log in (found 2026-11-03)
880
+ const loggedOut = runnerId === "claude" && claudeConfigSet(runEnv) !== undefined && claudeLoggedIn(runEnv, runner.command) === false;
881
+ if (loggedOut) {
882
+ stopCode = "auth";
883
+ stopValues = { command: claudeLoginCommand(runEnv) };
884
+ }
885
+ for (let i = 0; i < (loggedOut ? 0 : max); i++) {
878
886
  if (stop.signal.aborted || finishing) {
879
887
  stopCode = "user";
880
888
  break;
@@ -1233,7 +1241,7 @@ register(updatedDuringRun({
1233
1241
  }
1234
1242
  if (result.outcome === "auth") {
1235
1243
  stopCode = "auth";
1236
- stopValues = { command: runner.command };
1244
+ stopValues = { command: runnerId === "claude" ? claudeLoginCommand(runEnv) : runner.command };
1237
1245
  break;
1238
1246
  }
1239
1247
  // the agent does not take its model: said with what to do, never only its raw error