strom-research 1.24.0-beta.2 → 1.25.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.cs.md +16 -4
- package/CHANGELOG.de.md +17 -4
- package/CHANGELOG.md +16 -4
- package/assets/lang/cs.json +1 -0
- package/assets/lang/de.json +1 -0
- package/dist/cli/main.js +3 -0
- package/dist/cli/ui.js +1 -0
- package/dist/commands/setup.js +4 -0
- package/dist/commands/start.js +1 -1
- package/dist/core/json.js +10 -4
- package/dist/core/live.js +2 -2
- package/dist/core/stromapp.js +2 -2
- package/dist/core/tree.js +1 -1
- package/dist/core/uninstall.js +33 -3
- package/dist/core/widget.js +106 -32
- package/package.json +1 -1
package/CHANGELOG.cs.md
CHANGED
|
@@ -5,14 +5,24 @@
|
|
|
5
5
|
Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou na
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (anglicky).
|
|
7
7
|
|
|
8
|
+
## 1.25.0
|
|
9
|
+
|
|
10
|
+
*Připravuje se: bety do 1.25.0-beta.1. Oddíl bude úplný s vydáním.*
|
|
11
|
+
|
|
12
|
+
- **Data pro widget Strom i na Windows.** Výzkum teď zapisuje to, co z něj ukazuje widget Strom (na čem se pracuje,
|
|
13
|
+
nálezy za posledních 24 hodin, co čeká), i na Windows, do `%USERPROFILE%\.config\strom\widget`, se značkou verze
|
|
14
|
+
strom, která je zapsala. Widget, který zrovna čte svůj soubor, most nikdy nezdrží. `strom uninstall` odebere i soubory
|
|
15
|
+
widgetu, také na Macu; výzkum a nastavení zůstávají.
|
|
16
|
+
|
|
8
17
|
## 1.24.0
|
|
9
18
|
|
|
10
|
-
*
|
|
19
|
+
*31. 10. 2026 · patří k aplikaci Strom 3.19.0.*
|
|
11
20
|
|
|
12
21
|
- **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
22
|
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
|
|
14
23
|
výzkumy ho přiřazují stejně; přechází jen přiřazení místa, nic z rodiny. Místo stejného jména, které jistě totéž
|
|
15
|
-
není, dostane agent jako návrh; sám přiřazuje jen zbytek. `strom history adopt --dry-run` řekne, co by se převzalo.
|
|
24
|
+
není, dostane agent jako návrh; sám přiřazuje jen zbytek. `strom history adopt --dry-run` řekne, co by se převzalo. Otázka na zapnutí nebo vypnutí událostí
|
|
25
|
+
dějin říká, že platí pro všechny výzkumy na tomto počítači, i jak to změnit jen pro jeden výzkum.
|
|
16
26
|
- **Opravy výzkumu pro aplikaci Strom.** Most vypíše, co `strom fixes` ve výzkumu nabízí (název, co dělá, kolik
|
|
17
27
|
položek, stav, jestli jde vrátit), zdarma a bez zápisu; odkaz `strom-research://fixes` otevře opravu v terminálu
|
|
18
28
|
výzkumu, kde se oprava i její vrácení vždy nejdřív potvrzuje.
|
|
@@ -21,12 +31,14 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
21
31
|
vynechává, a u pramenů bez výřezu. Jen veřejná webová stránka, nikdy adresa fondu; standardní soubor GEDCOM nechává
|
|
22
32
|
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
33
|
půlnoci.
|
|
34
|
+
- **Vlastní rod osoby z aplikace Strom.** Když aplikace Strom u osoby označí, že zakládá vlastní rod nebo rod
|
|
35
|
+
rodiny nenese, `strom sync` to uloží do výzkumu a soubor pro Strom to vrací, takže další odeslání i jiné zařízení
|
|
36
|
+
ukážou stejné rody; sloučení dvou osob značku zachová. Standardní soubor GEDCOM o tom nic neříká. Potřebuje aplikaci
|
|
37
|
+
Strom 3.19.0.
|
|
24
38
|
- **Čí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;
|
|
25
39
|
ulice s číslem jde do souboru tak, jak je. Věk „05y“ se čte jako 5 let. Obojí se z aplikace Strom vrací stejné,
|
|
26
40
|
není to úprava uživatele. Období, které vede aplikace Strom („1871/1882“, nebo otevřené na jednom konci), přijde do
|
|
27
41
|
výzkumu jako od … do …, nikdy jako datum někde mezi nimi.
|
|
28
|
-
- **Otázka na dějiny říká, kde platí.** Zapnutí nebo vypnutí událostí dějin platí pro všechny výzkumy na tomto
|
|
29
|
-
počítači; otázka to říká, i jak to změnit jen pro jeden výzkum.
|
|
30
42
|
- **Slabý nebo odmítnutý model agenta hlásí doctor.** Když výzkum nemá zvolený model a vlastní výchozí model agenta je
|
|
31
43
|
ten, který výzkum shledal pro svou práci slabým nebo odmítnutým, `strom doctor` varuje a dá příkaz k volbě; sám
|
|
32
44
|
nikdy nevolí.
|
package/CHANGELOG.de.md
CHANGED
|
@@ -5,15 +5,26 @@
|
|
|
5
5
|
Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Installationszeilen stehen bei
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (auf Englisch).
|
|
7
7
|
|
|
8
|
+
## 1.25.0
|
|
9
|
+
|
|
10
|
+
*In Vorbereitung: die Betas bis 1.25.0-beta.1. Der Abschnitt ist mit dem Release vollständig.*
|
|
11
|
+
|
|
12
|
+
- **Daten für das Strom-Widget auch unter Windows.** Eine Forschung schreibt jetzt, was das Strom-Widget von ihr zeigt
|
|
13
|
+
(woran gearbeitet wird, die Funde der letzten 24 Stunden, was wartet), auch unter Windows, nach
|
|
14
|
+
`%USERPROFILE%\.config\strom\widget`, mit einer Marke des strom, der es geschrieben hat. Ein Widget, das gerade seine
|
|
15
|
+
Datei liest, hält die Brücke nie auf. `strom uninstall` entfernt auch die Dateien des Widgets, auch auf dem Mac; die
|
|
16
|
+
Forschung und die Einstellungen bleiben.
|
|
17
|
+
|
|
8
18
|
## 1.24.0
|
|
9
19
|
|
|
10
|
-
*
|
|
20
|
+
*31.10.2026 · gehört zur Strom-App 3.19.0.*
|
|
11
21
|
|
|
12
22
|
- **Orte einmal zugeordnet für jede Forschung auf diesem Computer.** Ein Ort, den eine andere Forschung hier schon den
|
|
13
23
|
Regionen der Geschichte zugeordnet hat, wird übernommen, wenn es sicher derselbe Ort ist (derselbe Name und dieselben
|
|
14
24
|
Koordinaten, Pfarrei oder derselbe Bezirk) und jede Forschung ihn gleich zuordnet; nur die Zuordnung des Orts geht
|
|
15
25
|
über, nichts von der Familie. Ein Ort gleichen Namens, der nicht sicher derselbe ist, geht als Vorschlag an den
|
|
16
|
-
Agenten; er ordnet nur den Rest zu. `strom history adopt --dry-run` sagt, was übernommen würde.
|
|
26
|
+
Agenten; er ordnet nur den Rest zu. `strom history adopt --dry-run` sagt, was übernommen würde. Die Frage, die Ereignisse der Geschichte ein- oder
|
|
27
|
+
auszuschalten, sagt, dass es für jede Forschung auf diesem Computer gilt, und wie es sich für eine allein ändern lässt.
|
|
17
28
|
- **Die Korrekturen einer Forschung für die Strom-App.** Die Brücke listet, was `strom fixes` in der Forschung anbietet
|
|
18
29
|
(Titel, was sie tut, wie viele Einträge, ihr Stand, ob sie sich zurücknehmen lässt), kostenlos und ohne etwas zu
|
|
19
30
|
schreiben; der Link `strom-research://fixes` öffnet eine Korrektur im Terminal der Forschung, wo sie und ihre
|
|
@@ -23,12 +34,14 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
23
34
|
Ausschnitten, die die Datei auslässt, und bei Quellen ohne Ausschnitt. Nur eine öffentliche Webseite, nie die Adresse
|
|
24
35
|
eines Bestands; die Standard-GEDCOM-Datei behält die Adresse in der Notiz. Das Datum der Version der
|
|
25
36
|
Forschung in der Strom-Datei trägt auch die Uhrzeit, so zeigt die App den Tag ihrer Entstehung auch um Mitternacht.
|
|
37
|
+
- **Die eigene Linie einer Person aus der Strom-App.** Markiert die Strom-App, dass eine Person eine eigene Linie
|
|
38
|
+
gründet oder die der Familie nicht trägt, behält `strom sync` das in der Forschung, und die Strom-Datei gibt es
|
|
39
|
+
zurück, so zeigen die nächste Sendung und ein anderes Gerät dieselben Linien; das Zusammenführen zweier Personen
|
|
40
|
+
behält die Markierung. Die Standard-GEDCOM-Datei sagt nichts davon. Es braucht die Strom-App 3.19.0.
|
|
26
41
|
- **Hausnummer, Alter und Zeitraum, wie sie geschrieben wurden.** Das Wort für eine Hausnummer („No.“, „čp.“) steht nur vor
|
|
27
42
|
einer Zahl allein; eine Straße mit Nummer geht so in die Datei, wie sie ist. Ein Alter „05y“ gilt als 5 Jahre.
|
|
28
43
|
Beides kommt aus der Strom-App gleich zurück, keine Änderung des Benutzers. Ein Zeitraum der Strom-App
|
|
29
44
|
(„1871/1882“ oder an einem Ende offen) kommt als von … bis … in die Forschung, nie als ein Datum irgendwo dazwischen.
|
|
30
|
-
- **Die Frage nach der Geschichte sagt, wo sie gilt.** Die Ereignisse der Geschichte ein- oder auszuschalten gilt für
|
|
31
|
-
jede Forschung auf diesem Computer; die Frage sagt es, und wie es sich für eine Forschung allein ändern lässt.
|
|
32
45
|
- **Ein schwaches oder abgelehntes Modell des Agenten meldet der Doctor.** Ist für die Forschung kein Modell gewählt und
|
|
33
46
|
das eigene Standardmodell des Agenten eines, das die Forschung für ihre Arbeit schwach oder abgelehnt fand, warnt
|
|
34
47
|
`strom doctor` und nennt den Befehl zur Wahl; selbst wählt er nie.
|
package/CHANGELOG.md
CHANGED
|
@@ -5,15 +5,25 @@ English · [Čeština](CHANGELOG.cs.md) · [Deutsch](CHANGELOG.de.md)
|
|
|
5
5
|
The changes of each version in short. The full notes and the install lines are on
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases).
|
|
7
7
|
|
|
8
|
+
## 1.25.0
|
|
9
|
+
|
|
10
|
+
*In preparation: the betas up to 1.25.0-beta.1. The section is complete with the release.*
|
|
11
|
+
|
|
12
|
+
- **Data for the Strom widget on Windows too.** A research now writes what the Strom widget shows of it (what it works
|
|
13
|
+
on, the finds of the last 24 hours, what waits) also on Windows, into `%USERPROFILE%\.config\strom\widget`, with a
|
|
14
|
+
mark of the strom that wrote it. A widget reading its file never holds up the bridge. `strom uninstall` takes the
|
|
15
|
+
widget's files too, on a Mac as well; the research and the settings stay.
|
|
16
|
+
|
|
8
17
|
## 1.24.0
|
|
9
18
|
|
|
10
|
-
*
|
|
19
|
+
*2026-10-31 · goes with the Strom app 3.19.0.*
|
|
11
20
|
|
|
12
21
|
- **Places mapped once for every research on this computer.** A place another research here has mapped to its
|
|
13
22
|
regions of history is taken over when it is surely the same place (the same name and the same coordinates, parish
|
|
14
23
|
or district) and every research maps it alike; only the place's map crosses, nothing of the family. A place of the
|
|
15
24
|
same name that is not surely the same is offered to the agent as a suggestion; the agent maps only the rest.
|
|
16
|
-
`strom history adopt --dry-run` says what would be taken.
|
|
25
|
+
`strom history adopt --dry-run` says what would be taken. The question about turning the events of history on or off
|
|
26
|
+
says it holds for every research on this computer, and how to change it for one research alone.
|
|
17
27
|
- **The fixes of a research for the Strom app.** The bridge lists what `strom fixes` offers in the research (title,
|
|
18
28
|
what it does, how many items, its state, whether it can be put back), free and without writing anything; the link
|
|
19
29
|
`strom-research://fixes` opens a fix in the research's terminal, where setting it right or putting it back is
|
|
@@ -23,12 +33,14 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
23
33
|
file leaves out and for the sources without an excerpt. Only a public web page, never a recordset's address; the
|
|
24
34
|
standard GEDCOM file keeps the address in the note. The date of the research's version in the Strom file carries its
|
|
25
35
|
time too, so the app shows the day it was made also around midnight.
|
|
36
|
+
- **A person's own lineage from the Strom app.** Where the Strom app marks that a person founds a lineage of their
|
|
37
|
+
own or carries none of the family's, `strom sync` keeps it in the research and the Strom file gives it back, so the
|
|
38
|
+
next send and another device show the same lineages; a merge of two people keeps the mark. The standard GEDCOM file
|
|
39
|
+
says nothing of it. It needs the Strom app 3.19.0.
|
|
26
40
|
- **A house number, an age and a period as they were written.** The word for a house number ("No.", "čp.") is put only before
|
|
27
41
|
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.
|
|
28
42
|
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
43
|
open at one end) comes into the research as from … to …, never as a date somewhere between.
|
|
30
|
-
- **The question about history says where it holds.** Turning the events of history on or off holds for every
|
|
31
|
-
research on this computer; the question says so, and how to change it for one research alone.
|
|
32
44
|
- **A weak or refused model of the agent said by the doctor.** When no model is chosen for the research and the
|
|
33
45
|
agent's own default is one the research found weak or refused for its work, `strom doctor` warns and gives the
|
|
34
46
|
command to choose one; it never chooses itself.
|
package/assets/lang/cs.json
CHANGED
|
@@ -881,6 +881,7 @@
|
|
|
881
881
|
"ui.uninstall.links": "odkazy z aplikace Strom",
|
|
882
882
|
"ui.uninstall.git": "vlastní git stromu",
|
|
883
883
|
"ui.uninstall.path": "záznam instalátoru v PATH",
|
|
884
|
+
"ui.uninstall.widget": "soubory widgetu Strom",
|
|
884
885
|
"ui.uninstall.program": "program",
|
|
885
886
|
"ui.uninstall.list": "strom z počítače odebere:",
|
|
886
887
|
"ui.uninstall.keeps": "Výzkumy zůstávají ({home}), stejně jako nastavení s klíči, které je pečetí ({config}) – smazat je ručně, jen pokud už nikdy nebudou potřeba.",
|
package/assets/lang/de.json
CHANGED
|
@@ -881,6 +881,7 @@
|
|
|
881
881
|
"ui.uninstall.links": "die Links aus der Strom-App",
|
|
882
882
|
"ui.uninstall.git": "strom's eigenes git",
|
|
883
883
|
"ui.uninstall.path": "der PATH-Eintrag des Installationsprogramms",
|
|
884
|
+
"ui.uninstall.widget": "die Dateien des Strom-Widgets",
|
|
884
885
|
"ui.uninstall.program": "das Programm",
|
|
885
886
|
"ui.uninstall.list": "strom entfernt von diesem Computer:",
|
|
886
887
|
"ui.uninstall.keeps": "Die Forschungen bleiben ({home}), ebenso die Einstellungen mit den Schlüsseln, die sie versiegeln ({config}) – sie nur dann selbst löschen, wenn sie nie wieder gebraucht werden.",
|
package/dist/cli/main.js
CHANGED
|
@@ -38,6 +38,7 @@ import { expandFromLine, refreshLinks } from "../core/links.js";
|
|
|
38
38
|
import { clockLine, FINISH_LINE, finishLimitLine, finishOf, timeLine } from "../core/clock.js";
|
|
39
39
|
import { currentSession } from "../core/session.js";
|
|
40
40
|
import { backgroundOf } from "../core/fetching.js";
|
|
41
|
+
import { markWidget } from "../core/widget.js";
|
|
41
42
|
import { callMistake, checkArgs, GroupOnly, parseOptions, resolveCommand, firstWord, severalRecords, splitPassthrough, usageOf } from "./execute.js";
|
|
42
43
|
import { placeholders, UI, ui } from "./ui.js";
|
|
43
44
|
import { catalog, localized, placeholdersOf, sayCommandAs } from "../core/phrases.js";
|
|
@@ -410,6 +411,8 @@ async function mainOf(argv, io, env, cwd) {
|
|
|
410
411
|
ctx.settings.config.lastChannel = channel;
|
|
411
412
|
ctx.settings.save();
|
|
412
413
|
refreshGlobal(env);
|
|
414
|
+
// …and the widget on the desktop (macOS, Windows) learns this strom writes its files: index.json, even with no tree
|
|
415
|
+
markWidget(env);
|
|
413
416
|
// …and the links from the Strom app lead to this strom again (where strom made them on this person's yes —
|
|
414
417
|
// never another installation's: one with its own settings asks)
|
|
415
418
|
try {
|
package/dist/cli/ui.js
CHANGED
|
@@ -718,6 +718,7 @@ export const UI = {
|
|
|
718
718
|
"ui.uninstall.links": "the links from the Strom app",
|
|
719
719
|
"ui.uninstall.git": "strom's own git",
|
|
720
720
|
"ui.uninstall.path": "the installer's PATH entry",
|
|
721
|
+
"ui.uninstall.widget": "the Strom widget's files",
|
|
721
722
|
"ui.uninstall.program": "the program",
|
|
722
723
|
"ui.uninstall.list": "strom takes this off the computer:",
|
|
723
724
|
"ui.uninstall.keeps": "The research stays ({home}), and so do the settings with the keys that seal it ({config}) — delete them by hand only if they are never wanted again.",
|
package/dist/commands/setup.js
CHANGED
|
@@ -68,6 +68,7 @@ import { ensureHooksDir } from "../core/hooks.js";
|
|
|
68
68
|
import { claudeInChrome, CLAUDE_IN_CHROME_URL, downloadsDir } from "../core/browser.js";
|
|
69
69
|
import { browserConnectors, fenceKeepsNetOff, listConnectors } from "../core/connector.js";
|
|
70
70
|
import { appReadLimits } from "../core/live-read.js";
|
|
71
|
+
import { markWidget } from "../core/widget.js";
|
|
71
72
|
export const SHARED_DIRS = ["media", "catalog", "tools", "cache", "inbox"];
|
|
72
73
|
export function ensureShared(dir) {
|
|
73
74
|
for (const d of SHARED_DIRS)
|
|
@@ -94,6 +95,8 @@ register({
|
|
|
94
95
|
const s = ctx.settings;
|
|
95
96
|
if (ctx.interactive && !isAgent(ctx.env)) {
|
|
96
97
|
const r = await setupWizard(ctx, { research: Boolean(opts.research) });
|
|
98
|
+
// the widget on the desktop (macOS, Windows) learns this strom writes its files
|
|
99
|
+
markWidget(ctx.env);
|
|
97
100
|
// run in an archive (the Strom app's link to the research's settings too): research offered beside it
|
|
98
101
|
let root;
|
|
99
102
|
try {
|
|
@@ -184,6 +187,7 @@ register({
|
|
|
184
187
|
cfg.trees = trees;
|
|
185
188
|
s.save();
|
|
186
189
|
fs.mkdirSync(home, { recursive: true });
|
|
190
|
+
markWidget(ctx.env);
|
|
187
191
|
const sharedDir = s.shared().value;
|
|
188
192
|
const treesDir = s.trees().value;
|
|
189
193
|
fs.mkdirSync(treesDir, { recursive: true });
|
package/dist/commands/start.js
CHANGED
|
@@ -497,7 +497,7 @@ register({
|
|
|
497
497
|
},
|
|
498
498
|
}, {
|
|
499
499
|
path: ["uninstall"],
|
|
500
|
-
summary: "Take strom off this computer: what the agents were taught, the shortcut, its own git, its PATH lines and the program — the research and the settings stay",
|
|
500
|
+
summary: "Take strom off this computer: what the agents were taught, the shortcut, its own git, its PATH lines, the widget's files and the program — the research and the settings stay",
|
|
501
501
|
group: "setup",
|
|
502
502
|
description: "The user's decision: a person at their terminal is asked there (--yes: no question); asked by an agent, strom asks\n" +
|
|
503
503
|
"in a window of the system. Installed through npm, the program itself goes with npm uninstall -g strom-research.",
|
package/dist/core/json.js
CHANGED
|
@@ -42,7 +42,7 @@ export function stringifyCanonical(value) {
|
|
|
42
42
|
export const RENAME_PATIENCE = 1500;
|
|
43
43
|
const RENAME_FIRST_WAIT = 25;
|
|
44
44
|
/** What another program holding the target (an antivirus, the search indexer, a backup) makes a rename fail with. */
|
|
45
|
-
const HELD = new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
45
|
+
export const HELD = new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
46
46
|
/**
|
|
47
47
|
* fn, again for a while on Windows when a file it uses is held: an antivirus, the indexer or a sync tool that has the
|
|
48
48
|
* file open for a moment makes a rename, a read or a removal fail with EPERM, EBUSY or EACCES. The waits grow (25, 50,
|
|
@@ -88,14 +88,20 @@ export function readFileRetrying(file, platform = process.platform) {
|
|
|
88
88
|
export function removeRetrying(file, platform = process.platform) {
|
|
89
89
|
retryingHeld(file, platform, () => fs.rmSync(file, { force: true }));
|
|
90
90
|
}
|
|
91
|
-
/**
|
|
92
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Write via a temp file in the same directory and rename: never a half file. `patient: false`: the rename tried once,
|
|
93
|
+
* never waited for a file another program holds (the widget's files: the bridge's tick is never held up by a reader).
|
|
94
|
+
*/
|
|
95
|
+
export function writeFileAtomic(file, content, opts = {}) {
|
|
93
96
|
const dir = path.dirname(file);
|
|
94
97
|
fs.mkdirSync(dir, { recursive: true });
|
|
95
98
|
const tmp = path.join(dir, `.${path.basename(file)}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`);
|
|
96
99
|
fs.writeFileSync(tmp, content, "utf8");
|
|
97
100
|
try {
|
|
98
|
-
|
|
101
|
+
if (opts.patient === false)
|
|
102
|
+
fs.renameSync(tmp, file);
|
|
103
|
+
else
|
|
104
|
+
renameRetrying(tmp, file);
|
|
99
105
|
}
|
|
100
106
|
catch (err) {
|
|
101
107
|
fs.rmSync(tmp, { force: true });
|
package/dist/core/live.js
CHANGED
|
@@ -3727,8 +3727,8 @@ export function serveLive(root, env) {
|
|
|
3727
3727
|
return atWorkNow;
|
|
3728
3728
|
};
|
|
3729
3729
|
let workers = safe("the start", () => JSON.stringify(atWork(seen))) ?? "";
|
|
3730
|
-
// the research on the desktop of a Mac
|
|
3731
|
-
// (core/widget.ts; none
|
|
3730
|
+
// the research on the desktop (the Strom Widget of a Mac, the widget of Windows 11): the tree's overview written
|
|
3731
|
+
// while the bridge runs (core/widget.ts; none but on macOS and Windows) — the first a moment after the start, never in its way
|
|
3732
3732
|
const widget = widgetWriter(root, env);
|
|
3733
3733
|
if (widget)
|
|
3734
3734
|
setTimeout(() => safe("the widget", () => widget.tick(true)), 1000).unref?.();
|
package/dist/core/stromapp.js
CHANGED
|
@@ -562,11 +562,11 @@ export function appKnowsPrimaryName(settings, version) {
|
|
|
562
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
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
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).
|
|
565
|
+
* nothing of them: no header mark). Released with the app 3.19.0; an app of unknown version (strom app from a
|
|
566
566
|
* terminal) stays off, as the groups of a surname's forms; another copy of the app (its beta, its development) gets
|
|
567
567
|
* them from APP_READS_LINES_BETA.
|
|
568
568
|
*/
|
|
569
|
-
export const APP_READS_LINES =
|
|
569
|
+
export const APP_READS_LINES = "3.19.0";
|
|
570
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
571
|
export const APP_READS_LINES_BETA = "3.19.0-beta.4";
|
|
572
572
|
/** Does the Strom app of this version keep the bounds of a lineage the person set (_STROM_LINE, _STROM_LINES)? */
|
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.
|
|
32
|
+
export const VERSION = "1.25.0-beta.1";
|
|
33
33
|
const GITIGNORE = `# Strom Research — local state and large files are not versioned
|
|
34
34
|
.strom/
|
|
35
35
|
/media/
|
package/dist/core/uninstall.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// What strom put on this computer outside the research, and taking it away
|
|
2
2
|
// again (strom uninstall): what the agents were taught, the shortcut, strom's
|
|
3
|
-
// own git, the PATH lines of the installer
|
|
4
|
-
//
|
|
5
|
-
// are the user's; strom
|
|
3
|
+
// own git, the PATH lines of the installer, the widget's files (what it
|
|
4
|
+
// showed of the research) and the program itself. The research stays, and so
|
|
5
|
+
// do the settings with the keys that seal it — they are the user's; strom
|
|
6
|
+
// says where they are.
|
|
6
7
|
import fs from "node:fs";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -13,6 +14,7 @@ import { globalTargets, isInstalled, uninstallGlobal } from "../agents/global.js
|
|
|
13
14
|
import { PROFILES } from "../agents/profiles.js";
|
|
14
15
|
import { holdsPath, installation, pathForms, PROGRAM_ENTRIES } from "./self.js";
|
|
15
16
|
import { linkFiles, linkOwner, unregisterLinks } from "./links.js";
|
|
17
|
+
import { widgetDir } from "./widget.js";
|
|
16
18
|
/** The mark the installer (install/install.sh) puts on the PATH line it adds. */
|
|
17
19
|
const MARK = "# strom research";
|
|
18
20
|
const sys = (cmd, args) => {
|
|
@@ -328,6 +330,11 @@ export function uninstallPlan(env, names, opts = {}) {
|
|
|
328
330
|
// none
|
|
329
331
|
}
|
|
330
332
|
}
|
|
333
|
+
// the widget's files (macOS, Windows): only what strom wrote there, the folder when nothing else is left in it — the
|
|
334
|
+
// widget would show the research as not at work for ever
|
|
335
|
+
const widget = widgetDir(env, platform);
|
|
336
|
+
if (widget && widgetFiles(widget).length && !holdsKept(widget, keep, platform))
|
|
337
|
+
remove.push({ kind: "widget", path: widget, remove: () => removeWidgetFiles(widget) });
|
|
331
338
|
const program = inst.kind === "installed" ? inst.root : undefined;
|
|
332
339
|
// install.json's launchers: those of a launcher's name outside the research only
|
|
333
340
|
const chosen = launchersToTake(inst.launchers ?? [], keep, platform);
|
|
@@ -346,3 +353,26 @@ export function uninstallPlan(env, names, opts = {}) {
|
|
|
346
353
|
const skipped = chosen.skipped.filter((l) => !pathForms(l, platform).some((f) => taken.has(f)));
|
|
347
354
|
return { remove, npm: inst.kind === "npm", ...(program ? { program, launchers: chosen.take, skipped } : {}) };
|
|
348
355
|
}
|
|
356
|
+
/** What strom writes into the widget's folder: index.json, each tree's <ID>.json and a file being written beside one. */
|
|
357
|
+
const WIDGET_FILE = /^(?:index|[0-9A-Za-z-]{1,64})\.json$|^\..+\.json\.\d+\.[0-9a-f]+\.tmp$/u;
|
|
358
|
+
export function widgetFiles(dir) {
|
|
359
|
+
try {
|
|
360
|
+
return fs.readdirSync(dir).filter((n) => WIDGET_FILE.test(n) && fs.statSync(path.join(dir, n)).isFile());
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
return [];
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/** The widget's files taken; the folder too when nothing else is in it (never what strom did not write there). */
|
|
367
|
+
export function removeWidgetFiles(dir) {
|
|
368
|
+
for (const n of widgetFiles(dir))
|
|
369
|
+
fs.rmSync(path.join(dir, n), { force: true });
|
|
370
|
+
try {
|
|
371
|
+
if (!fs.readdirSync(dir).length)
|
|
372
|
+
fs.rmdirSync(dir);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// gone already, or another program's file in it: it stays
|
|
376
|
+
}
|
|
377
|
+
return !widgetFiles(dir).length;
|
|
378
|
+
}
|
package/dist/core/widget.js
CHANGED
|
@@ -4,20 +4,22 @@
|
|
|
4
4
|
// phone-finds.ts) and cleaned as for it (forPhone): never a path, a record's ID, a spend or a model. Beside each text
|
|
5
5
|
// that names a person, the same without the names (the widget's "hide names").
|
|
6
6
|
//
|
|
7
|
-
// Written by the tree's bridge (strom live) while it runs, on macOS
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// folder — a research of a test
|
|
7
|
+
// Written by the tree's bridge (strom live) while it runs, on macOS (~/Library/Application Support/Strom/widget/) and
|
|
8
|
+
// Windows (%USERPROFILE%\.config\strom\widget\, the Windows 11 widget of strom-win): <tree id>.json and index.json
|
|
9
|
+
// (the trees there), each whole (a file beside it renamed over it). Looked at every WIDGET_CHECK_MS, written when what
|
|
10
|
+
// it says changed, else every WIDGET_HEARTBEAT_MS; the bridge ending as it should writes `running: false`, its research
|
|
11
|
+
// gone takes the file away. An archive has none. The widget only reads: nothing of it comes back. An isolated
|
|
12
|
+
// installation (STROM_ISOLATED: a test's, a second strom) writes into its own settings folder — a research of a test
|
|
13
|
+
// never shows in the person's widget. index.json is there from this strom's first run and its setup on (`strom`: the
|
|
14
|
+
// version that wrote it), so the widget tells a strom too old to write any from one whose bridge has not run yet.
|
|
13
15
|
import fs from "node:fs";
|
|
14
16
|
import path from "node:path";
|
|
15
17
|
import { configDir, isolated, userHome } from "./paths.js";
|
|
16
18
|
import { Settings } from "./config.js";
|
|
17
|
-
import { Tree } from "./tree.js";
|
|
19
|
+
import { Tree, VERSION } from "./tree.js";
|
|
18
20
|
import { actsAsArchive } from "./state.js";
|
|
19
21
|
import { foldText } from "./text.js";
|
|
20
|
-
import { writeFileAtomic } from "./json.js";
|
|
22
|
+
import { writeFileAtomic, HELD } from "./json.js";
|
|
21
23
|
import { appUrlSetting } from "./stromapp.js";
|
|
22
24
|
import { forPhone, treeOverview } from "./phone-watch.js";
|
|
23
25
|
import { treeFinds } from "./phone-finds.js";
|
|
@@ -37,17 +39,23 @@ export const WIDGET_PENDING = 3;
|
|
|
37
39
|
/** What stands for a name taken out of a text. */
|
|
38
40
|
const HIDDEN = "…";
|
|
39
41
|
/**
|
|
40
|
-
* Where the widget's files are written: none but on macOS; STROM_WIDGET_DIR when set; an isolated
|
|
41
|
-
* own settings folder; else ~/Library/Application Support/Strom/widget of the HOME strom
|
|
42
|
+
* Where the widget's files are written: none but on macOS and Windows; STROM_WIDGET_DIR when set; an isolated
|
|
43
|
+
* installation into its own settings folder; else on macOS ~/Library/Application Support/Strom/widget of the HOME strom
|
|
44
|
+
* runs with, on Windows %USERPROFILE%\.config\strom\widget — the profile's own folder (HOME may be Git Bash's), also
|
|
45
|
+
* with settings elsewhere (STROM_CONFIG_DIR), as the Mac's ~/Library. Never under AppData: an app packaged as MSIX
|
|
46
|
+
* (Claude's desktop app, whose agent starts the bridge) gets every new file there as a private copy of its own, which
|
|
47
|
+
* the widget would never see (found 2026-10-30 with the seal keys; core/paths.ts configDir).
|
|
42
48
|
*/
|
|
43
49
|
export function widgetDir(env, platform = process.platform) {
|
|
44
|
-
if (platform !== "darwin")
|
|
50
|
+
if (platform !== "darwin" && platform !== "win32")
|
|
45
51
|
return undefined;
|
|
46
52
|
const said = env.STROM_WIDGET_DIR?.trim();
|
|
47
53
|
if (said)
|
|
48
54
|
return path.resolve(said);
|
|
49
55
|
if (isolated(env))
|
|
50
56
|
return path.join(configDir(env, platform), "widget");
|
|
57
|
+
if (platform === "win32")
|
|
58
|
+
return path.join(env.USERPROFILE || userHome(env), ".config", "strom", "widget");
|
|
51
59
|
return path.join(userHome(env), "Library", "Application Support", "Strom", "widget");
|
|
52
60
|
}
|
|
53
61
|
/** A moment as the widget reads it: ISO 8601 to the second, in UTC (Swift's .iso8601 reads no fractions). */
|
|
@@ -318,15 +326,37 @@ export function widgetTree(root, env, opts) {
|
|
|
318
326
|
const treeFile = (dir, key) => path.join(dir, `${key}.json`);
|
|
319
327
|
/** A tree's key as a file's name: the tree's ID (a UUID) — never a path. */
|
|
320
328
|
const KEY = /^[0-9A-Za-z-]{1,64}$/u;
|
|
321
|
-
/**
|
|
329
|
+
/**
|
|
330
|
+
* A file of the widget written whole (a file beside it renamed over it) — at once or not now: the widget reading it this
|
|
331
|
+
* moment holds it on Windows (a rename over an open file fails with EPERM or EBUSY there), and the bridge never waits
|
|
332
|
+
* for it (writeFileAtomic's patience would hold the bridge's tick for 1.5 s). False: not written, nothing left beside
|
|
333
|
+
* it; the next look writes it.
|
|
334
|
+
*/
|
|
335
|
+
function writeNow(file, content) {
|
|
336
|
+
try {
|
|
337
|
+
writeFileAtomic(file, content, { patient: false });
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
if (HELD.has(err.code ?? ""))
|
|
342
|
+
return false;
|
|
343
|
+
throw err;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* index.json as the files of the trees there say it, written whole when it says something else (or another strom wrote
|
|
348
|
+
* it); the folder made when there is none (the mark of this strom: strom's first run, its setup). False: held by the
|
|
349
|
+
* widget this moment, not written.
|
|
350
|
+
*/
|
|
322
351
|
export function writeWidgetIndex(dir, now = Date.now()) {
|
|
323
352
|
const trees = [];
|
|
324
353
|
let names = [];
|
|
325
354
|
try {
|
|
326
355
|
names = fs.readdirSync(dir).filter((n) => n.endsWith(".json") && n !== "index.json" && !n.startsWith("."));
|
|
327
356
|
}
|
|
328
|
-
catch {
|
|
329
|
-
|
|
357
|
+
catch (err) {
|
|
358
|
+
if (err.code !== "ENOENT")
|
|
359
|
+
return true;
|
|
330
360
|
}
|
|
331
361
|
for (const n of names.sort()) {
|
|
332
362
|
try {
|
|
@@ -342,34 +372,64 @@ export function writeWidgetIndex(dir, now = Date.now()) {
|
|
|
342
372
|
const file = path.join(dir, "index.json");
|
|
343
373
|
try {
|
|
344
374
|
const was = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
345
|
-
if (JSON.stringify(was.trees) === JSON.stringify(trees) && was.schemaVersion === WIDGET_SCHEMA)
|
|
346
|
-
return;
|
|
375
|
+
if (JSON.stringify(was.trees) === JSON.stringify(trees) && was.schemaVersion === WIDGET_SCHEMA && was.strom === VERSION)
|
|
376
|
+
return true;
|
|
347
377
|
}
|
|
348
378
|
catch {
|
|
349
379
|
// none yet
|
|
350
380
|
}
|
|
351
|
-
|
|
381
|
+
return writeNow(file, `${JSON.stringify({ schemaVersion: WIDGET_SCHEMA, generatedAt: isoSecond(now), strom: VERSION, trees }, null, 2)}\n`);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* The mark that this strom writes the widget's files (on macOS and Windows): index.json, with no tree in it when no
|
|
385
|
+
* bridge ran yet — written at strom's first run of a version and at its setup. A widget that finds none tells a strom too
|
|
386
|
+
* old to write any (strom update) from one whose bridge has not run yet. Never in the way: any failure said nowhere.
|
|
387
|
+
*/
|
|
388
|
+
export function markWidget(env, platform = process.platform) {
|
|
389
|
+
const dir = widgetDir(env, platform);
|
|
390
|
+
if (!dir)
|
|
391
|
+
return;
|
|
392
|
+
try {
|
|
393
|
+
writeWidgetIndex(dir);
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
// written by the next bridge
|
|
397
|
+
}
|
|
352
398
|
}
|
|
353
|
-
/** A tree's file written whole (a file beside it renamed over it), then index.json. */
|
|
399
|
+
/** A tree's file written whole (a file beside it renamed over it), then index.json. False: held by the widget, not now. */
|
|
354
400
|
export function writeWidgetTree(dir, data) {
|
|
355
401
|
if (!KEY.test(data.treeKey))
|
|
356
|
-
return;
|
|
357
|
-
|
|
358
|
-
|
|
402
|
+
return true;
|
|
403
|
+
if (!writeNow(treeFile(dir, data.treeKey), `${JSON.stringify(data, null, 2)}\n`))
|
|
404
|
+
return false;
|
|
405
|
+
return writeWidgetIndex(dir, Date.parse(data.generatedAt) || Date.now());
|
|
359
406
|
}
|
|
360
|
-
/**
|
|
361
|
-
|
|
407
|
+
/**
|
|
408
|
+
* A tree's file taken away (its research gone, an archive now), and index.json without it. One the widget holds without
|
|
409
|
+
* letting it go (Windows) stays: it says at least `running: false` (as `last` was, or as it says), not a research at work
|
|
410
|
+
* for the quarter of an hour the widget takes to call it old.
|
|
411
|
+
*/
|
|
412
|
+
export function dropWidgetTree(dir, key, last) {
|
|
362
413
|
if (!KEY.test(key))
|
|
363
414
|
return;
|
|
415
|
+
const file = treeFile(dir, key);
|
|
364
416
|
try {
|
|
365
|
-
fs.rmSync(
|
|
417
|
+
fs.rmSync(file, { force: true });
|
|
366
418
|
}
|
|
367
419
|
catch {
|
|
420
|
+
try {
|
|
421
|
+
const was = last ?? JSON.parse(fs.readFileSync(file, "utf8"));
|
|
422
|
+
const at = isoSecond(Date.now());
|
|
423
|
+
writeWidgetTree(dir, { ...was, running: false, generatedAt: at, lastActiveAt: at });
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// nothing more to do: the widget calls it old by itself
|
|
427
|
+
}
|
|
368
428
|
return;
|
|
369
429
|
}
|
|
370
430
|
writeWidgetIndex(dir);
|
|
371
431
|
}
|
|
372
|
-
/** The writer of a tree's bridge; none
|
|
432
|
+
/** The writer of a tree's bridge; none but on macOS and Windows. */
|
|
373
433
|
export function widgetWriter(root, env, platform = process.platform, clock = Date.now) {
|
|
374
434
|
const dir = widgetDir(env, platform);
|
|
375
435
|
if (!dir)
|
|
@@ -381,40 +441,54 @@ export function widgetWriter(root, env, platform = process.platform, clock = Dat
|
|
|
381
441
|
let said = "";
|
|
382
442
|
let last;
|
|
383
443
|
let key;
|
|
444
|
+
// what the widget held at the last look (Windows: renamed over an open file): written at the next tick, not the next look
|
|
445
|
+
let held;
|
|
384
446
|
// what moves with the clock alone says nothing new
|
|
385
447
|
const content = (t) => JSON.stringify({ ...t, generatedAt: "", lastActiveAt: "", findings: { ...t.findings, windowStart: "" } });
|
|
448
|
+
const write = (t, c, now) => {
|
|
449
|
+
if (!writeWidgetTree(dir, t)) {
|
|
450
|
+
held = { t, c };
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
held = undefined;
|
|
454
|
+
said = c;
|
|
455
|
+
written = now;
|
|
456
|
+
};
|
|
386
457
|
return {
|
|
387
458
|
tick(force = false) {
|
|
388
459
|
const now = clock();
|
|
389
|
-
if (!force && now - checked < checkMs)
|
|
460
|
+
if (!force && now - checked < checkMs) {
|
|
461
|
+
if (held)
|
|
462
|
+
write({ ...held.t, generatedAt: isoSecond(now), lastActiveAt: isoSecond(now) }, held.c, now);
|
|
390
463
|
return;
|
|
464
|
+
}
|
|
391
465
|
checked = now;
|
|
392
466
|
const t = widgetTree(root, env, { running: true, now });
|
|
393
467
|
if (!t) {
|
|
394
468
|
// an archive now: nothing of it on the desktop
|
|
395
469
|
if (key)
|
|
396
|
-
dropWidgetTree(dir, key);
|
|
470
|
+
dropWidgetTree(dir, key, last);
|
|
397
471
|
key = undefined;
|
|
398
472
|
last = undefined;
|
|
473
|
+
held = undefined;
|
|
399
474
|
said = "";
|
|
400
475
|
return;
|
|
401
476
|
}
|
|
402
477
|
key = t.treeKey;
|
|
403
478
|
last = t;
|
|
404
479
|
const c = content(t);
|
|
405
|
-
if (c === said && now - written < beatMs)
|
|
480
|
+
if (c === said && now - written < beatMs && !held)
|
|
406
481
|
return;
|
|
407
|
-
|
|
408
|
-
said = c;
|
|
409
|
-
written = now;
|
|
482
|
+
write(t, c, now);
|
|
410
483
|
},
|
|
411
484
|
end(gone) {
|
|
412
485
|
if (!key)
|
|
413
486
|
return;
|
|
414
487
|
// what the file says now is no longer what was written: the next look writes it again
|
|
415
488
|
said = "";
|
|
489
|
+
held = undefined;
|
|
416
490
|
if (gone)
|
|
417
|
-
return dropWidgetTree(dir, key);
|
|
491
|
+
return dropWidgetTree(dir, key, last && { ...last, running: false });
|
|
418
492
|
if (!last)
|
|
419
493
|
return;
|
|
420
494
|
const at = isoSecond(clock());
|