hmgr 0.0.42 → 0.0.43
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/dist/adapter/gui-signal.js +29 -0
- package/dist/aenderungen.json +3 -0
- package/dist/cli/hilfe.js +7 -0
- package/dist/cli/main.js +37 -16
- package/dist/gui/main.js +80 -0
- package/dist/gui/preload.cjs +5 -0
- package/dist/gui/renderer/renderer.js +41 -0
- package/dist/gui/signal.js +58 -0
- package/dist/kern/anwendungsfaelle.js +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Signaldatei von der CLI an eine laufende GUI (E110): `<hmgrHome>/gui-signal.json`. Die CLI
|
|
2
|
+
// schreibt sie nach `dir add` und Verwandten, die GUI beobachtet das Verzeichnis (src/gui/signal.ts).
|
|
3
|
+
// Kein Port: der Kern kennt das Signal nicht, die Entscheidung „Signal ja/nein" fällt in der CLI.
|
|
4
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { ohneBom } from "./datei-text.js";
|
|
7
|
+
export const SIGNAL_DATEI = 'gui-signal.json';
|
|
8
|
+
/** Atomar: Temp-Datei im selben Verzeichnis, dann rename darüber. */
|
|
9
|
+
export async function signalSchreiben(hmgrHome, signal) {
|
|
10
|
+
await mkdir(hmgrHome, { recursive: true });
|
|
11
|
+
const ziel = path.join(hmgrHome, SIGNAL_DATEI);
|
|
12
|
+
const temp = path.join(hmgrHome, `${SIGNAL_DATEI}.${process.pid}.tmp`);
|
|
13
|
+
await writeFile(temp, `${JSON.stringify(signal)}\n`);
|
|
14
|
+
await rename(temp, ziel);
|
|
15
|
+
}
|
|
16
|
+
/** `undefined` bei fehlender, kaputter oder unbekannt geformter Datei. */
|
|
17
|
+
export async function signalLesen(hmgrHome) {
|
|
18
|
+
try {
|
|
19
|
+
const roh = await readFile(path.join(hmgrHome, SIGNAL_DATEI), 'utf8');
|
|
20
|
+
const daten = JSON.parse(ohneBom(roh));
|
|
21
|
+
if (daten.art !== 'verzeichnis-zeigen' || typeof daten.pfad !== 'string' || typeof daten.zeit !== 'number') {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return { art: daten.art, pfad: daten.pfad, zeit: daten.zeit };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/aenderungen.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"fassung": 1,
|
|
3
3
|
"versionen": {
|
|
4
|
+
"0.0.43": [
|
|
5
|
+
"Fügst du im Terminal ein Arbeitsverzeichnis hinzu (hmgr dir add oder link mit einem neuen Verzeichnis), kommt ein offenes hmgr-Fenster nach vorn und zeigt das Verzeichnis sofort an."
|
|
6
|
+
],
|
|
4
7
|
"0.0.42": [
|
|
5
8
|
"Der Verlauf meldet jetzt auch neue und entfernte Skills einer Quelle, nicht nur geänderte.",
|
|
6
9
|
"Verlauf und „hmgr update“ sind leichter zu lesen: Quellen erscheinen mit ihrem Namen, darunter Neu, Entfernt und Geändert, ohne Commit-Kürzel. Quellen ohne Änderung an deinen Skills stehen gesammelt in einer Zeile am Ende."
|
package/dist/cli/hilfe.js
CHANGED
|
@@ -104,6 +104,10 @@ Plugins des Vorhabens stehen. Ohne Zeiger bleibt das Verzeichnis gemerkt und jed
|
|
|
104
104
|
(E26). dir forget löst Bindung und Eintrag, die gelegten Links bleiben liegen. Gebunden wird nur für
|
|
105
105
|
Harnesses, die hmgr verwaltet: standardmäßig nur claude-code (E38).
|
|
106
106
|
|
|
107
|
+
Läuft die hmgr-GUI, kommt sie bei dir add nach vorn und zeigt das Verzeichnis (E110). Dasselbe
|
|
108
|
+
passiert bei link, unlink, adopt, bundle add und plugin add mit --ort dir:<pfad>, wenn das Verzeichnis
|
|
109
|
+
dabei neu gemerkt wird. Über SSH bleibt die GUI in Ruhe, und läuft keine GUI, passiert nichts.
|
|
110
|
+
|
|
107
111
|
Ein Arbeitsverzeichnis unter Windows, das in einer WSL-Distro liegt (\\\\wsl$\\<distro>\\… oder
|
|
108
112
|
\\\\wsl.localhost\\<distro>\\…, E96), bedient hmgr über die Bridge dieser Distro, wie ein Homedir in
|
|
109
113
|
WSL: der Link liegt im Linux-Dateisystem und zeigt auf den Windows-Checkout (/mnt/c). MCP-Server
|
|
@@ -135,6 +139,9 @@ nur für ein Arbeitsverzeichnis mit composer.json, an Home und Profil bleibt jed
|
|
|
135
139
|
Das Schreiben in einen Zeiger ist gesperrt, bis das Composer-Thema entschieden ist (E57): an einem
|
|
136
140
|
Arbeitsverzeichnis mit Zeiger bricht link ohne --local ab, der Link wird nicht gelegt.
|
|
137
141
|
|
|
142
|
+
Mit --ort dir:<pfad> kommt eine laufende hmgr-GUI nach vorn und zeigt das Verzeichnis, wenn es dabei
|
|
143
|
+
neu gemerkt wird (nicht per SSH, E110).
|
|
144
|
+
|
|
138
145
|
Schalter:
|
|
139
146
|
--harness <name> claude-code, hermes oder agents, Standard claude-code
|
|
140
147
|
--ort <ort> home, wsl:<distro>, dir:<pfad> oder profil:<name>, Standard home. profil
|
package/dist/cli/main.js
CHANGED
|
@@ -21,6 +21,7 @@ import { paketVersion } from "../adapter/paket-version.js";
|
|
|
21
21
|
import { quelleAlsText, quelleAusText } from "../adapter/quelle-text.js";
|
|
22
22
|
import { TerminalRueckfrage } from "../adapter/terminal.js";
|
|
23
23
|
import { ProzessUmgebung } from "../adapter/umgebung.js";
|
|
24
|
+
import { signalSchreiben } from "../adapter/gui-signal.js";
|
|
24
25
|
import { ZustandJson } from "../adapter/zustand-json.js";
|
|
25
26
|
import { VerlaufJson } from "../adapter/verlauf-json.js";
|
|
26
27
|
import { AenderungenJson } from "../adapter/aenderungen-json.js";
|
|
@@ -259,6 +260,21 @@ const UEBERNAHME_GRUND = {
|
|
|
259
260
|
'lokale-aenderungen': 'Ordner weicht von der Quelle ab',
|
|
260
261
|
'nicht-pruefbar': 'hmgr kann nicht prüfen, ob der Ordner der Quelle entspricht',
|
|
261
262
|
};
|
|
263
|
+
/**
|
|
264
|
+
* E110: gibt einer laufenden GUI Bescheid, dass dieses Verzeichnis gezeigt werden soll. Nie bei
|
|
265
|
+
* SSH-Aufrufen, nie ein Grund zu scheitern.
|
|
266
|
+
*/
|
|
267
|
+
async function guiBenachrichtigen(umgebung, ports, pfad) {
|
|
268
|
+
try {
|
|
269
|
+
const variablen = ports.umgebung.variablen();
|
|
270
|
+
if ((variablen.SSH_CONNECTION ?? '') !== '' || (variablen.SSH_TTY ?? '') !== '')
|
|
271
|
+
return;
|
|
272
|
+
await signalSchreiben(umgebung.hmgrHome, { art: 'verzeichnis-zeigen', pfad, zeit: Date.now() });
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// Das Signal ist ein Zusatz; das Kommando selbst hat funktioniert.
|
|
276
|
+
}
|
|
277
|
+
}
|
|
262
278
|
export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
263
279
|
const { anwendungsfaelle, ports } = bauen(umgebung, optionen);
|
|
264
280
|
const kommando = argv[0];
|
|
@@ -426,6 +442,7 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
426
442
|
const ergebnis = composer === undefined
|
|
427
443
|
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(pfad)
|
|
428
444
|
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(pfad, composer);
|
|
445
|
+
await guiBenachrichtigen(umgebung, ports, ergebnis.pfad);
|
|
429
446
|
console.log(`Arbeitsverzeichnis ${ergebnis.pfad}`);
|
|
430
447
|
if (ergebnis.zeigerPfad !== undefined)
|
|
431
448
|
console.log(` Zeiger ${ergebnis.zeigerPfad}`);
|
|
@@ -465,10 +482,11 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
465
482
|
if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
466
483
|
// Verzeichnis merken und binden, damit status es wiederfindet und der Zeiger bekannt ist (E21).
|
|
467
484
|
const composer = werte.get('composer');
|
|
468
|
-
|
|
469
|
-
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
470
|
-
|
|
471
|
-
|
|
485
|
+
const verz = composer === undefined
|
|
486
|
+
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
487
|
+
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad, composer);
|
|
488
|
+
if (verz.neu)
|
|
489
|
+
await guiBenachrichtigen(umgebung, ports, verz.pfad);
|
|
472
490
|
}
|
|
473
491
|
if (kommando === 'unlink') {
|
|
474
492
|
const ergebnis = await anwendungsfaelle.linkEntfernen({ skill, ziel, lokal });
|
|
@@ -504,10 +522,11 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
504
522
|
const name = rest[0];
|
|
505
523
|
if (ziel.ort.art === 'arbeitsverzeichnis' && !probelauf) {
|
|
506
524
|
const composer = werte.get('composer');
|
|
507
|
-
|
|
508
|
-
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
509
|
-
|
|
510
|
-
|
|
525
|
+
const verz = composer === undefined
|
|
526
|
+
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
527
|
+
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad, composer);
|
|
528
|
+
if (verz.neu)
|
|
529
|
+
await guiBenachrichtigen(umgebung, ports, verz.pfad);
|
|
511
530
|
}
|
|
512
531
|
const ergebnis = await anwendungsfaelle.uebernehmen({
|
|
513
532
|
ziel,
|
|
@@ -568,10 +587,11 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
568
587
|
const lokal = flaggen.has('local') || flaggen.has('lokal');
|
|
569
588
|
if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
570
589
|
const composer = werte.get('composer');
|
|
571
|
-
|
|
572
|
-
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
573
|
-
|
|
574
|
-
|
|
590
|
+
const verz = composer === undefined
|
|
591
|
+
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
592
|
+
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad, composer);
|
|
593
|
+
if (verz.neu)
|
|
594
|
+
await guiBenachrichtigen(umgebung, ports, verz.pfad);
|
|
575
595
|
}
|
|
576
596
|
const quelle = werte.get('quelle');
|
|
577
597
|
const ergebnis = unterkommando === 'add'
|
|
@@ -627,10 +647,11 @@ export async function ausfuehren(argv, umgebung, optionen = {}) {
|
|
|
627
647
|
if (ziel.ort.art === 'arbeitsverzeichnis') {
|
|
628
648
|
// Wie bei link: Verzeichnis merken und binden, damit status es wiederfindet (E21).
|
|
629
649
|
const composer = werte.get('composer');
|
|
630
|
-
|
|
631
|
-
await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
632
|
-
|
|
633
|
-
|
|
650
|
+
const verz = composer === undefined
|
|
651
|
+
? await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad)
|
|
652
|
+
: await anwendungsfaelle.arbeitsverzeichnisHinzufuegen(ziel.ort.pfad, composer);
|
|
653
|
+
if (verz.neu)
|
|
654
|
+
await guiBenachrichtigen(umgebung, ports, verz.pfad);
|
|
634
655
|
}
|
|
635
656
|
if (unterkommando === 'add') {
|
|
636
657
|
const quelle = werte.get('quelle');
|
package/dist/gui/main.js
CHANGED
|
@@ -21,6 +21,8 @@ import { iconFuerQuelle } from "./icons.js";
|
|
|
21
21
|
import { entscheideOeffnen } from "./oeffnen.js";
|
|
22
22
|
import { mcpUebernahmeSerialisieren, mcpUebersichtSerialisieren, mcpWirkungSerialisieren } from "./mcp-ansicht.js";
|
|
23
23
|
import { beobachten } from "./beobachter.js";
|
|
24
|
+
import { signalSchreiben } from "../adapter/gui-signal.js";
|
|
25
|
+
import { signalBeobachten } from "./signal.js";
|
|
24
26
|
import { loginPathUebernehmen } from "./login-pfad.js";
|
|
25
27
|
import { hmgrCliVersion } from "./hmgr-cli.js";
|
|
26
28
|
import { cliUpdateHinweis } from "../adapter/npm-registry.js";
|
|
@@ -505,6 +507,7 @@ function neuLadenAnstossenFabrik(minAbstandMs) {
|
|
|
505
507
|
}
|
|
506
508
|
const neuLadenAnstossen = neuLadenAnstossenFabrik(FOKUS_RELOAD_MIN_ABSTAND_MS);
|
|
507
509
|
let beobachter;
|
|
510
|
+
let signalBeobachter;
|
|
508
511
|
function fensterOeffnen() {
|
|
509
512
|
hauptfenster = new BrowserWindow({
|
|
510
513
|
width: 1280,
|
|
@@ -1367,6 +1370,63 @@ function fensterOeffnen() {
|
|
|
1367
1370
|
await new Promise((r) => setTimeout(r, 800));
|
|
1368
1371
|
const auszug = await fenster.webContents.executeJavaScript(NEUSTART_AUSZUG_SKRIPT);
|
|
1369
1372
|
console.log(`[smoke] Neustart-Modal: ${String(auszug)}`);
|
|
1373
|
+
})
|
|
1374
|
+
// E110: CLI-Signal gegen die laufende GUI. Das Signal schreibt dieser Main-Prozess in das
|
|
1375
|
+
// Wegwerf-Home (der Renderer kann keine Dateien schreiben). Fall 1: ohne Dialog springt die
|
|
1376
|
+
// GUI aus einer anderen Ansicht zur Karte. Fall 2: mit offenem Dialog bleibt alles stehen,
|
|
1377
|
+
// erst nach dem Schließen folgt der Sprung. Läuft nach der Neustart-Modal-Prüfung, weil der Fall
|
|
1378
|
+
// offene Dialoge schließt.
|
|
1379
|
+
.then(async () => {
|
|
1380
|
+
const pfade = (process.env.HMGR_GUI_SMOKE_SIGNAL_PFADE ?? '').split('\n').filter((p) => p !== '');
|
|
1381
|
+
if (pfade.length < 2)
|
|
1382
|
+
return;
|
|
1383
|
+
const [pfad1, pfad2] = pfade;
|
|
1384
|
+
const js = (code) => fenster.webContents.executeJavaScript(code);
|
|
1385
|
+
const karteJs = (pfad) => `(function () {
|
|
1386
|
+
var schluessel = ${JSON.stringify(`dir:${pfad}`)};
|
|
1387
|
+
var karte = Array.from(document.querySelectorAll('#zielliste .ziel.verzeichnis'))
|
|
1388
|
+
.find(function (k) { return k.dataset.verzKey === schluessel; });
|
|
1389
|
+
return {
|
|
1390
|
+
karte: karte !== undefined,
|
|
1391
|
+
zieleSichtbar: document.getElementById('ansicht-ziele').hidden === false,
|
|
1392
|
+
aufgeklappt: karte?.querySelector('.zielkopf')?.getAttribute('aria-expanded') ?? null,
|
|
1393
|
+
};
|
|
1394
|
+
})()`;
|
|
1395
|
+
const warteAufSprung = async (pfad) => {
|
|
1396
|
+
let stand = null;
|
|
1397
|
+
for (let ms = 0; ms < 4000; ms += 100) {
|
|
1398
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1399
|
+
stand = await js(karteJs(pfad));
|
|
1400
|
+
const k = stand;
|
|
1401
|
+
if (k.zieleSichtbar && k.aufgeklappt === 'true')
|
|
1402
|
+
break;
|
|
1403
|
+
}
|
|
1404
|
+
return stand;
|
|
1405
|
+
};
|
|
1406
|
+
const ergebnis = {};
|
|
1407
|
+
try {
|
|
1408
|
+
await js(`new Promise(function (r) { var n = 0; (function w() { if (document.querySelector('dialog[open]') || ++n > 30) r(); else setTimeout(w, 50); })(); })`);
|
|
1409
|
+
await js(`document.querySelectorAll('dialog[open]').forEach(function (d) { d.close(); });
|
|
1410
|
+
document.querySelector('[data-ansicht="mcp"]').click(); true`);
|
|
1411
|
+
ergebnis.fall1Vorher = await js(karteJs(pfad1));
|
|
1412
|
+
await signalSchreiben(umgebung.hmgrHome, { art: 'verzeichnis-zeigen', pfad: pfad1, zeit: Date.now() });
|
|
1413
|
+
ergebnis.fall1 = await warteAufSprung(pfad1);
|
|
1414
|
+
// Fall 2: Dialog offen, andere Ansicht, Signal für ein anderes Verzeichnis.
|
|
1415
|
+
await js(`document.querySelector('[data-ansicht="mcp"]').click();
|
|
1416
|
+
document.getElementById('d-einstellungen').showModal(); true`);
|
|
1417
|
+
ergebnis.fall2Vorher = await js(karteJs(pfad2));
|
|
1418
|
+
await signalSchreiben(umgebung.hmgrHome, { art: 'verzeichnis-zeigen', pfad: pfad2, zeit: Date.now() });
|
|
1419
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
1420
|
+
ergebnis.fall2Offen = await js(`({ dialogOffen: document.getElementById('d-einstellungen').open === true, ` +
|
|
1421
|
+
`ansichtMcp: document.getElementById('ansicht-mcp').hidden === false })`);
|
|
1422
|
+
ergebnis.fall2NachWarten = await js(karteJs(pfad2));
|
|
1423
|
+
await js(`document.getElementById('d-einstellungen').close(); true`);
|
|
1424
|
+
ergebnis.fall2 = await warteAufSprung(pfad2);
|
|
1425
|
+
}
|
|
1426
|
+
catch (fehler) {
|
|
1427
|
+
ergebnis.fehler = String(fehler);
|
|
1428
|
+
}
|
|
1429
|
+
console.log(`[smoke] Signal: ${JSON.stringify(ergebnis)}`);
|
|
1370
1430
|
})
|
|
1371
1431
|
.finally(() => app.quit());
|
|
1372
1432
|
}, 3000);
|
|
@@ -1407,6 +1467,26 @@ function fensterOeffnen() {
|
|
|
1407
1467
|
beobachter = undefined;
|
|
1408
1468
|
});
|
|
1409
1469
|
}
|
|
1470
|
+
// CLI-Signal (E110): `hmgr dir add` und Verwandte schreiben `gui-signal.json` ins hmgr-Home. Anders
|
|
1471
|
+
// als der Watcher oben ohne Entprellen und ohne Drossel; läuft auch im Smoke-Test (mit dessen
|
|
1472
|
+
// Wegwerf-Home), dort aber ohne Fokusklau.
|
|
1473
|
+
{
|
|
1474
|
+
const fenster = hauptfenster;
|
|
1475
|
+
signalBeobachter = signalBeobachten(umgebung.hmgrHome, (pfad) => {
|
|
1476
|
+
if (fenster.isDestroyed())
|
|
1477
|
+
return;
|
|
1478
|
+
if (!smoketest) {
|
|
1479
|
+
fensterNachVornHolen();
|
|
1480
|
+
if (process.platform === 'darwin')
|
|
1481
|
+
app.focus({ steal: true });
|
|
1482
|
+
}
|
|
1483
|
+
fenster.webContents.send('hmgr:verzeichnisZeigen', pfad);
|
|
1484
|
+
});
|
|
1485
|
+
fenster.on('closed', () => {
|
|
1486
|
+
signalBeobachter?.close();
|
|
1487
|
+
signalBeobachter = undefined;
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1410
1490
|
void hauptfenster.loadFile(path.join(hierVerzeichnis, 'renderer', 'index.html'));
|
|
1411
1491
|
}
|
|
1412
1492
|
const { autoUpdater } = electronUpdater;
|
package/dist/gui/preload.cjs
CHANGED
|
@@ -79,6 +79,11 @@ contextBridge.exposeInMainWorld('hmgr', {
|
|
|
79
79
|
onNeuLadenAnstossen: (hoerer) => {
|
|
80
80
|
ipcRenderer.on('hmgr:neuLadenAnstossen', () => hoerer());
|
|
81
81
|
},
|
|
82
|
+
/** Signal der CLI (E110): ein Arbeitsverzeichnis soll gezeigt werden. Main hat das Fenster schon
|
|
83
|
+
nach vorn geholt; kein Entprellen, keine Drossel. */
|
|
84
|
+
onVerzeichnisZeigen: (hoerer) => {
|
|
85
|
+
ipcRenderer.on('hmgr:verzeichnisZeigen', (_event, pfad) => hoerer(pfad));
|
|
86
|
+
},
|
|
82
87
|
/** Prüfung auf neue hmgr-Stände (E77), angestoßen bei jedem Aktualisieren-Lauf (E80: Start,
|
|
83
88
|
Intervall, Klick). Antwortet nicht, meldet sich später über onSelbstUpdate — Netz und Spawn
|
|
84
89
|
dauern länger als ein Knopfdruck. */
|
|
@@ -2804,6 +2804,7 @@
|
|
|
2804
2804
|
knopf.classList.remove('laeuft');
|
|
2805
2805
|
// Scheiterte der Lauf, bliebe sonst „Prüft …" stehen.
|
|
2806
2806
|
if ($('standtext').textContent === 'Prüft …') standText();
|
|
2807
|
+
reloadWartendeFreigeben();
|
|
2807
2808
|
}
|
|
2808
2809
|
}
|
|
2809
2810
|
|
|
@@ -2883,6 +2884,21 @@
|
|
|
2883
2884
|
// überholt. Fehler landen wie sonst über rufe() in der Meldungsleiste, kein stiller Absturz.
|
|
2884
2885
|
var stillerReloadLaeuft = false;
|
|
2885
2886
|
var stillerReloadVorgemerkt = false;
|
|
2887
|
+
// Wer auf das Ende des Neuladens wartet (E110): die Auflöser werden frei, sobald weder ein
|
|
2888
|
+
// stiller Reload noch ein Aktualisieren-Lauf (der mit ladeAlles endet) mehr läuft.
|
|
2889
|
+
var reloadWartende = [];
|
|
2890
|
+
function reloadWartendeFreigeben() {
|
|
2891
|
+
if (stillerReloadLaeuft || stillerReloadVorgemerkt || $('updatestart').disabled) return;
|
|
2892
|
+
var liste = reloadWartende;
|
|
2893
|
+
reloadWartende = [];
|
|
2894
|
+
liste.forEach(function (aufloeser) { aufloeser(); });
|
|
2895
|
+
}
|
|
2896
|
+
function stillerReloadAbwarten() {
|
|
2897
|
+
return new Promise(function (aufloeser) {
|
|
2898
|
+
reloadWartende.push(aufloeser);
|
|
2899
|
+
void stillerReload();
|
|
2900
|
+
});
|
|
2901
|
+
}
|
|
2886
2902
|
async function stillerReload() {
|
|
2887
2903
|
// Läuft gerade ein Aktualisieren-Lauf, wird nicht parallel geladen: er endet ohnehin mit
|
|
2888
2904
|
// ladeAlles und nimmt die Änderung mit. Sonst bauten zwei Läufe gleichzeitig die Ansicht auf.
|
|
@@ -2901,11 +2917,36 @@
|
|
|
2901
2917
|
if (stillerReloadVorgemerkt) {
|
|
2902
2918
|
stillerReloadVorgemerkt = false;
|
|
2903
2919
|
void stillerReload();
|
|
2920
|
+
} else {
|
|
2921
|
+
reloadWartendeFreigeben();
|
|
2904
2922
|
}
|
|
2905
2923
|
}
|
|
2906
2924
|
}
|
|
2907
2925
|
window.hmgr.onNeuLadenAnstossen(function () { void stillerReload(); });
|
|
2908
2926
|
|
|
2927
|
+
// CLI-Signal (E110, nach `hmgr dir add` und Verwandten): sofort neu laden, dann zur Karte des
|
|
2928
|
+
// Verzeichnisses springen, auch aus einer anderen Ansicht. Steht ein Dialog offen, bleibt er
|
|
2929
|
+
// unangetastet und der Sprung wartet, bis kein Dialog mehr offen ist; ein neueres Signal ersetzt
|
|
2930
|
+
// die Vormerkung. Die Meldungsleiste bleibt, wie sie ist.
|
|
2931
|
+
var signalPfadVorgemerkt = null;
|
|
2932
|
+
function signalSprungEinloesen() {
|
|
2933
|
+
if (signalPfadVorgemerkt === null) return;
|
|
2934
|
+
if (document.querySelector('dialog[open]')) return;
|
|
2935
|
+
var pfad = signalPfadVorgemerkt;
|
|
2936
|
+
signalPfadVorgemerkt = null;
|
|
2937
|
+
// Eine E84-Vormerkung auf denselben Pfad ist mit diesem Sprung erledigt.
|
|
2938
|
+
if (zielSprungVorgemerkt === pfad) zielSprungVorgemerkt = null;
|
|
2939
|
+
zeigeAnsicht('ziele');
|
|
2940
|
+
springeZuVerzeichnis(pfad);
|
|
2941
|
+
}
|
|
2942
|
+
window.hmgr.onVerzeichnisZeigen(function (pfad) {
|
|
2943
|
+
signalPfadVorgemerkt = pfad;
|
|
2944
|
+
void stillerReloadAbwarten().then(signalSprungEinloesen);
|
|
2945
|
+
});
|
|
2946
|
+
// `close` bubbelt nicht, der Capture-Listener am Dokument sieht aber jeden geschlossenen Dialog.
|
|
2947
|
+
// Der Aufschub um einen Takt lässt einen unmittelbar folgenden Dialog zuerst aufgehen.
|
|
2948
|
+
document.addEventListener('close', function () { setTimeout(signalSprungEinloesen, 0); }, true);
|
|
2949
|
+
|
|
2909
2950
|
/**
|
|
2910
2951
|
* Eine Ereigniszeile im Verlauf (E80): Marke + Text, Muster wie die frühere baueBericht-Zeile
|
|
2911
2952
|
* und die CLI-Ausgabe (verlaufEreignisZeile, src/cli/main.ts). Checkouts stellt
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Beobachter für das CLI-Signal (E110), ohne Electron-Import (testbar wie beobachter.ts). Beobachtet
|
|
2
|
+
// wird das Verzeichnis hmgrHome mit Filter auf `gui-signal.json` (aus demselben Grund wie in
|
|
3
|
+
// beobachter.ts: die Datei wird per rename ersetzt, ein Watcher auf die Datei würde taub).
|
|
4
|
+
// Anders als beobachter.ts kein Entprellen und kein Inhaltsvergleich: jedes frische Signal zählt.
|
|
5
|
+
import { watch } from 'node:fs';
|
|
6
|
+
import { SIGNAL_DATEI, signalLesen } from "../adapter/gui-signal.js";
|
|
7
|
+
export function signalBeobachten(hmgrHome, callback, optionen = {}) {
|
|
8
|
+
const sammelnMs = optionen.sammelnMs ?? 30;
|
|
9
|
+
const maxAlterMs = optionen.maxAlterMs ?? 10_000;
|
|
10
|
+
let timer;
|
|
11
|
+
let geschlossen = false;
|
|
12
|
+
let zuletzt;
|
|
13
|
+
// Eine beim Start vorhandene Datei gilt als schon gesehen. Das Alter allein reicht nicht: auf macOS
|
|
14
|
+
// liefert fs.watch kurz nach dem Anlegen auch Events für Schreibvorgänge kurz davor.
|
|
15
|
+
const startStand = signalLesen(hmgrHome).then((signal) => {
|
|
16
|
+
if (signal !== undefined)
|
|
17
|
+
zuletzt = `${signal.zeit}\u0000${signal.pfad}`;
|
|
18
|
+
});
|
|
19
|
+
async function auswerten() {
|
|
20
|
+
await startStand;
|
|
21
|
+
const signal = await signalLesen(hmgrHome);
|
|
22
|
+
if (geschlossen || signal === undefined)
|
|
23
|
+
return;
|
|
24
|
+
if (Date.now() - signal.zeit > maxAlterMs)
|
|
25
|
+
return;
|
|
26
|
+
const schluessel = `${signal.zeit}\u0000${signal.pfad}`;
|
|
27
|
+
if (schluessel === zuletzt)
|
|
28
|
+
return;
|
|
29
|
+
zuletzt = schluessel;
|
|
30
|
+
callback(signal.pfad);
|
|
31
|
+
}
|
|
32
|
+
let watcher;
|
|
33
|
+
try {
|
|
34
|
+
watcher = watch(hmgrHome, (_ereignis, name) => {
|
|
35
|
+
if (geschlossen)
|
|
36
|
+
return;
|
|
37
|
+
if (name !== null && name !== SIGNAL_DATEI)
|
|
38
|
+
return;
|
|
39
|
+
if (timer !== undefined)
|
|
40
|
+
return; // schon ein Lesen eingeplant
|
|
41
|
+
timer = setTimeout(() => {
|
|
42
|
+
timer = undefined;
|
|
43
|
+
void auswerten();
|
|
44
|
+
}, sammelnMs);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Verzeichnis fehlt: unbeobachtet.
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
close() {
|
|
52
|
+
geschlossen = true;
|
|
53
|
+
if (timer !== undefined)
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
watcher?.close();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1322,7 +1322,8 @@ export class Anwendungsfaelle {
|
|
|
1322
1322
|
hinweis = `Kein Zeiger unter ${verzeichnis}, alles bleibt lokal (E26).`;
|
|
1323
1323
|
}
|
|
1324
1324
|
const zustand = await this.p.zustand.lesen();
|
|
1325
|
-
|
|
1325
|
+
const neu = !zustand.arbeitsverzeichnisse.includes(verzeichnis);
|
|
1326
|
+
if (neu)
|
|
1326
1327
|
zustand.arbeitsverzeichnisse.push(verzeichnis);
|
|
1327
1328
|
if (zeigerPfad !== undefined) {
|
|
1328
1329
|
const gebunden = zeigerPfad;
|
|
@@ -1338,6 +1339,7 @@ export class Anwendungsfaelle {
|
|
|
1338
1339
|
await this.p.zustand.schreiben(zustand);
|
|
1339
1340
|
return {
|
|
1340
1341
|
pfad: verzeichnis,
|
|
1342
|
+
neu,
|
|
1341
1343
|
...(zeigerPfad === undefined ? {} : { zeigerPfad }),
|
|
1342
1344
|
...(hinweis === undefined ? {} : { hinweis }),
|
|
1343
1345
|
};
|