iobroker.alias-workbench 0.9.0 → 0.9.7

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.
@@ -8,7 +8,9 @@ import { $ } from './basis.js';
8
8
  import { txt, tr } from './sprache.js';
9
9
  import './enums.js';
10
10
  import { zusatzName, setzeModus, zeichneBaum, merkeKlappstand } from './baum.js';
11
- import { kindZustaende, aliasQuellen, holeWerte } from './werte.js';
11
+ import { kindZustaende, aliasQuellen, holeWerte,
12
+ mitVorspann, schreibQuelle
13
+ } from './werte.js';
12
14
  import { erkenneEntwurf } from './erkennung.js';
13
15
  import { vorschlag , pruefeVorlage, zeigeAbozahl } from './vorlagen.js';
14
16
 
@@ -101,11 +103,7 @@ export function baueEntwurf(kanal) {
101
103
  alias.0.Badezimmer.Rollladen.CLOSE). Belegt ist das Schreiben
102
104
  durch `common.write` oder durch eine vorhandene Schreibformel -
103
105
  wer eine hinterlegt hat, wollte schreiben. */
104
- srcW: istAlias
105
- ? (q.einfach
106
- ? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
107
- : (q.write || ''))
108
- : (c.write ? id : ''),
106
+ srcW: istAlias ? schreibQuelle(o) : (c.write ? id : ''),
109
107
  f: (typeof a.read === 'string') ? a.read : '',
110
108
  fw: (typeof a.write === 'string') ? a.write : '',
111
109
  caption: istAlias ? txt(c.name) : kanalBeschriftung(kanal, kurzName),
@@ -132,8 +130,8 @@ export function baueEntwurf(kanal) {
132
130
  entstanden ist - dieselbe Regel wie in `waehle`. Ohne sie durfte eine
133
131
  spaeter angelegte Vorlage beim Umschalten auf die Alle-Ansicht das
134
132
  fertige Geraet still uebernehmen (26.08.2026). */
135
- export function alleUndVorlage(kanal, vorlagenId) {
136
- var v = vorschlag(kanal, vorlagenId);
133
+ export function alleUndVorlage(kanal, vorlagenId, instanz) {
134
+ var v = vorschlag(kanal, vorlagenId, instanz);
137
135
  var roh = baueEntwurf(kanal);
138
136
  if (!v) { return roh; }
139
137
 
@@ -550,7 +548,14 @@ export function steuerKanaele(dev) {
550
548
  var pflicht = f[0].states.filter(function (st) { return st.id; })
551
549
  .map(function (st) { return st.id; });
552
550
  if (!f[0].states.some(function (st) { return st.required && st.id; })) { return; }
553
- raus.push({ id: id, typ: f[0].type, name: eigenerKanalName(id) || id.slice(pre.length),
551
+ /* `name` ist der Anzeigename und darf alles enthalten, was ein Mensch
552
+ hinschreibt. `kennung` ist, was als Objekt-ID taugt — dieselbe
553
+ Saeuberung wie ueberall sonst (T19). Getrennt, weil beides
554
+ gebraucht wird: die Liste zeigt den Namen, das Ziel nimmt die
555
+ Kennung. */
556
+ var kn = eigenerKanalName(id);
557
+ raus.push({ id: id, typ: f[0].type, name: kn || id.slice(pre.length),
558
+ kennung: kennungtauglich(kn || '') || id.slice(pre.length),
554
559
  pflicht: pflicht });
555
560
  });
556
561
  kanalGeraeteCache[dev] = raus;
@@ -643,6 +648,20 @@ export function entwurfFuer(id, gemeinsam, gemAn) {
643
648
  return merkeHakenVorgabe(e2);
644
649
  }
645
650
 
651
+ /* Alle laufenden Abos abmelden.
652
+
653
+ Stand nur in `waehle` — und damit meldete kein einziger der Wege ab,
654
+ die die Auswahl fallen lassen: der Moduswechsel, das Zuklappen eines
655
+ Zweigs, das Abschalten des Expertenmodus, das Loeschen eines Alias.
656
+ Danach lief ein Abo auf ein Geraet, das rechts gar nicht mehr steht,
657
+ der Zaehler in der Kopfleiste behauptete „ein Abo", und `S.werte`
658
+ fuellte sich weiter (gemessen 09.09.2026). */
659
+ export function aboLoesen() {
660
+ (S.abo || []).forEach(function (m) { socket.emit('unsubscribe', m); });
661
+ S.abo = [];
662
+ zeigeAbozahl();
663
+ }
664
+
646
665
  export function waehle(id, fertig) {
647
666
  /* Wer links etwas anderes anklickt, will es rechts sehen - auch wenn
648
667
  der Fokus noch in einem Feld der rechten Seite steht. `tipptGerade`
@@ -702,11 +721,98 @@ export function waehle(id, fertig) {
702
721
  bekaeme mit der Zeit Ereignisse fuer das halbe System. Die Zahl der
703
722
  laufenden Abos steht in der Kopfleiste - dort sieht man sofort, ob
704
723
  sich etwas ansammelt. */
705
- (S.abo || []).forEach(function (m) { socket.emit('unsubscribe', m); });
724
+ aboLoesen();
706
725
  var muster = [id + '.*'];
707
726
  if (id.indexOf('alias.') === 0) {
708
- quellenVerteilung(id).forEach(function (q) {
709
- if (muster.indexOf(q.id + '.*') === -1) { muster.push(q.id + '.*'); }
727
+ /* Aus den Quellen, nicht aus der Anzeige-Verteilung.
728
+
729
+ `quellenVerteilung` fasst zusammen, was die rechte Seite als
730
+ „Quellen" zeigt — dafuer bildet sie den ELTERNknoten jeder
731
+ Quelle. Bei einem flachen MQTT-Geraet ist das der halbe Adapter:
732
+ gemessen 09.09.2026 wurde daraus `mqtt-client.0.*` mit 322
733
+ Objekten, an einem einzigen Alias. Und `S.werte` wird nie
734
+ geleert, das sammelt sich also.
735
+
736
+ Abonniert wird deshalb der Kanal jeder wirklich gelesenen Quelle.
737
+ Wo die Quellen ohnehin gebuendelt liegen, bleibt es bei einem
738
+ Muster; wo nicht, sind es ein paar mehr — aber jedes davon eng. */
739
+ var kanaele = [];
740
+ kindZustaende(id).forEach(function (kid) {
741
+ var q = aliasQuellen(S.objects[kid]);
742
+ [q.read, q.write].forEach(function (x) {
743
+ if (!x) { return; }
744
+ var t = String(x).split('.');
745
+ t.pop();
746
+ var kn = t.join('.');
747
+ if (kn && kanaele.indexOf(kn) === -1) { kanaele.push(kn); }
748
+ });
749
+ });
750
+ /* Ein schmaler Kanal wird als Muster abonniert, ein breiter nicht.
751
+
752
+ Bei einem flachen Geraet IST der Kanal der breite Knoten: Die
753
+ Quelle `mqtt-client.0.SmartHome.Lavalampe_POWER` liegt direkt
754
+ unter `SmartHome`, und `SmartHome.*` sind 320 Objekte. Ab dieser
755
+ Schwelle werden die Quellen einzeln abonniert — ein paar Abos
756
+ mehr, dafuer genau die, deren Werte auch angezeigt werden. */
757
+ var SCHWELLE = 30;
758
+ var wieBreit = function (knoten) {
759
+ var n = 0;
760
+ S.keysSorted.forEach(function (k0) { if (k0.indexOf(knoten + '.') === 0) { n++; } });
761
+ return n;
762
+ };
763
+ var drinSchon = function (was) {
764
+ return muster.some(function (m0) {
765
+ var p0 = m0.slice(-2) === '.*' ? m0.slice(0, -2) : m0;
766
+ return was === p0 || was.indexOf(p0 + '.') === 0;
767
+ });
768
+ };
769
+
770
+ /* Sparten desselben Geraets zu einem Muster zusammenfassen.
771
+
772
+ Ein Tasmota-Alias liest aus `…Licht.stat`, `…Licht.tele` und
773
+ `…Licht.cmnd` — drei Kanaele, ein Geraet, und in der Kopfleiste
774
+ gehoert dort „ein Abo" zu stehen, nicht drei. Zusammengefasst wird
775
+ aber nur, wo wirklich MEHRERE Kanaele unter demselben Knoten
776
+ haengen: Sonst waere aus `…Garten.Weihnachten_aussen` schlicht
777
+ `…Garten` geworden — breiter als noetig, ohne dass etwas
778
+ zusammenfaellt. */
779
+ for (var runde = 0; runde < 4; runde++) {
780
+ var unter = {};
781
+ kanaele.forEach(function (kn) {
782
+ var t = kn.split('.');
783
+ if (t.length <= 3) { return; }
784
+ var ob = t.slice(0, -1).join('.');
785
+ (unter[ob] = unter[ob] || []).push(kn);
786
+ });
787
+ var geaendert = false;
788
+ Object.keys(unter).forEach(function (ob) {
789
+ if (unter[ob].length < 2 || wieBreit(ob) > SCHWELLE) { return; }
790
+ kanaele = kanaele.filter(function (kn) { return unter[ob].indexOf(kn) === -1; });
791
+ if (kanaele.indexOf(ob) === -1) { kanaele.push(ob); }
792
+ geaendert = true;
793
+ });
794
+ if (!geaendert) { break; }
795
+ }
796
+
797
+ /* Ein schmaler Kanal wird als Muster abonniert, ein breiter nicht.
798
+
799
+ Bei einem flachen Geraet IST der Kanal der breite Knoten: Die
800
+ Quelle `mqtt-client.0.SmartHome.Lavalampe_POWER` liegt direkt
801
+ unter `SmartHome`, und `SmartHome.*` sind 320 Objekte (gemessen
802
+ 09.09.2026; vorher wurde daraus sogar `mqtt-client.0.*` mit 322).
803
+ Ab dieser Schwelle werden die Quellen einzeln abonniert — ein paar
804
+ Abos mehr, dafuer genau die, deren Werte auch angezeigt werden. */
805
+ kanaele.forEach(function (kn) {
806
+ if (drinSchon(kn)) { return; }
807
+ if (wieBreit(kn) > SCHWELLE) { return; }
808
+ muster.push(kn + '.*');
809
+ });
810
+ /* Was jetzt noch von keinem Muster gedeckt ist, kommt einzeln. */
811
+ kindZustaende(id).forEach(function (kid) {
812
+ var q = aliasQuellen(S.objects[kid]);
813
+ [q.read, q.write].forEach(function (x) {
814
+ if (x && !drinSchon(x) && muster.indexOf(x) === -1) { muster.push(x); }
815
+ });
710
816
  });
711
817
  }
712
818
  S.abo = muster;
@@ -736,7 +842,17 @@ export function waehle(id, fertig) {
736
842
  var werteDa = false;
737
843
  holeWerte(id, quellen, function () {
738
844
  werteDa = true;
739
- if (id !== S.current) { return; }
845
+ if (id !== S.current) {
846
+ /* Der Knoten hat gewechselt, waehrend die Werte unterwegs waren.
847
+ Am Entwurf ist hier nichts mehr zu tun — der Rueckruf muss aber
848
+ trotzdem kommen. Sonst bleibt `so59Laeuft` oder `abfrageLaeuft`
849
+ fuer immer gesetzt (der Knopf steht dauerhaft auf „laeuft"), und
850
+ der Trockenlauf nach dem Anlegen fehlender Sendepunkte geht nie
851
+ auf. Zwoelf Sekunden Wartezeit bei einer SetOption59-Abfrage
852
+ sind reichlich Gelegenheit dafuer (09.09.2026). */
853
+ if (typeof fertig === 'function') { fertig(); }
854
+ return;
855
+ }
740
856
 
741
857
  /* Wer schon Hand angelegt hat, behaelt seinen Entwurf.
742
858
 
@@ -948,13 +1064,40 @@ export function geraeteDarunter(wurzel) {
948
1064
  angelegt" und erzeugt beim Klick ein zweites Geraet, waehrend das
949
1065
  erste verwaist stehenbleibt. Beim Karbonator lag der Alias unter
950
1066
  alias.0.Strom, vorgeschlagen wurde alias.0.SmartHome. */
951
- export function aliasFuer(quelle) {
952
- var treffer = null;
953
- S.keysSorted.forEach(function (id) {
954
- if (treffer || id.indexOf('alias.') !== 0) { return; }
1067
+ /* Rueckwaertskarte Quelle -> Alias, einmal je Indexstand.
1068
+
1069
+ `aliasFuer` lief zweimal ueber alle Kennungen — und wird je
1070
+ Neuzeichnen mehrfach gerufen. Die Karte entsteht bei der ersten Frage
1071
+ nach einem Indexwechsel und beantwortet alle weiteren aus dem
1072
+ Gedaechtnis. Erkannt wird der Wechsel an Laenge und erster Kennung;
1073
+ `indexNeu()` sortiert ohnehin bei jeder Aenderung neu. */
1074
+ var alsQuelleKarte = null, alsQuelleStand = '';
1075
+ function alsQuelleKarten() {
1076
+ var stand = S.keysSorted.length + '|' + (S.keysSorted[0] || '');
1077
+ if (alsQuelleKarte && alsQuelleStand === stand) { return alsQuelleKarte; }
1078
+ var fest = {}, zweig = {};
1079
+ mitVorspann('alias.').forEach(function (id) {
955
1080
  var o = S.objects[id];
956
- if (o && o.native && o.native.quelle === quelle) { treffer = id; }
1081
+ if (!o) { return; }
1082
+ if (o.native && o.native.quelle && fest[o.native.quelle] === undefined) {
1083
+ fest[o.native.quelle] = id;
1084
+ }
1085
+ if (o.type !== 'state') { return; }
1086
+ var q = aliasQuellen(o);
1087
+ [q.read, q.write].forEach(function (x) {
1088
+ if (!x) { return; }
1089
+ var kanal = id.split('.').slice(0, -1).join('.');
1090
+ (zweig[x] = zweig[x] || []).push(kanal);
1091
+ });
957
1092
  });
1093
+ alsQuelleKarte = { fest: fest, zweig: zweig };
1094
+ alsQuelleStand = stand;
1095
+ return alsQuelleKarte;
1096
+ }
1097
+
1098
+ export function aliasFuer(quelle) {
1099
+ var karte = alsQuelleKarten();
1100
+ var treffer = karte.fest[quelle] || null;
958
1101
  if (treffer) { return treffer; }
959
1102
 
960
1103
  /* Welcher Aliaskanal liest oder schreibt am haeufigsten in diesen
@@ -963,17 +1106,19 @@ export function aliasFuer(quelle) {
963
1106
  erste Treffer. */
964
1107
  var zaehler = {}, bester = null, wieviel = 0;
965
1108
  var pre = quelle + '.';
966
- S.keysSorted.forEach(function (id) {
967
- if (id.indexOf('alias.') !== 0) { return; }
968
- var o = S.objects[id];
969
- if (!o || o.type !== 'state') { return; }
970
- var q = aliasQuellen(o);
971
- var passt = (q.read && q.read.indexOf(pre) === 0) ||
972
- (q.write && q.write.indexOf(pre) === 0);
973
- if (!passt) { return; }
974
- var eltern = id.slice(0, id.lastIndexOf('.'));
975
- zaehler[eltern] = (zaehler[eltern] || 0) + 1;
976
- if (zaehler[eltern] > wieviel) { wieviel = zaehler[eltern]; bester = eltern; }
1109
+ /* Aus der Karte: welche Aliaskanaele lesen oder schreiben in diesen
1110
+ Zweig? Die Quellen stehen dort als volle Kennung, gesucht ist alles
1111
+ darunter — also die Kennungen des Zweigs abklappern statt aller
1112
+ Aliase. */
1113
+ var karte2 = alsQuelleKarten();
1114
+ var kanaele = [];
1115
+ Object.keys(karte2.zweig).forEach(function (q0) {
1116
+ if (q0.indexOf(pre) !== 0) { return; }
1117
+ karte2.zweig[q0].forEach(function (kanal) { kanaele.push(kanal); });
1118
+ });
1119
+ kanaele.forEach(function (kanal) {
1120
+ zaehler[kanal] = (zaehler[kanal] || 0) + 1;
1121
+ if (zaehler[kanal] > wieviel) { wieviel = zaehler[kanal]; bester = kanal; }
977
1122
  });
978
1123
  return bester;
979
1124
  }
@@ -1033,9 +1178,7 @@ export function bestandVorrang(e, aliasId) {
1033
1178
  s.f = (typeof a.read === 'string') ? a.read : '';
1034
1179
  s.fw = (typeof a.write === 'string') ? a.write : '';
1035
1180
  if (q.read) { s.srcR = q.read; }
1036
- s.srcW = q.einfach
1037
- ? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
1038
- : (q.write || '');
1181
+ s.srcW = schreibQuelle(o);
1039
1182
  s.on = true;
1040
1183
  s.ausBestand = true;
1041
1184
  });
@@ -1072,9 +1215,7 @@ export function bestandsAbweichung(s, aliasId) {
1072
1215
  f: (typeof a.read === 'string') ? a.read : '',
1073
1216
  fw: (typeof a.write === 'string') ? a.write : '',
1074
1217
  srcR: q.read || '',
1075
- srcW: q.einfach
1076
- ? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
1077
- : (q.write || '')
1218
+ srcW: schreibQuelle(o)
1078
1219
  };
1079
1220
  var raus = [];
1080
1221
  ['role', 'typ', 'unit', 'f', 'fw', 'srcR', 'srcW'].forEach(function (k) {
@@ -1084,13 +1225,6 @@ export function bestandsAbweichung(s, aliasId) {
1084
1225
  return raus;
1085
1226
  }
1086
1227
 
1087
- /* Einen einzelnen Wert aus dem gespeicherten Alias zurueckholen. */
1088
- export function bestandsWert(s, aliasId, feld) {
1089
- var abw = bestandsAbweichung(s, aliasId);
1090
- var tr0 = null;
1091
- abw.forEach(function (x) { if (x.feld === feld) { tr0 = x; } });
1092
- return tr0 ? tr0.bestand : null;
1093
- }
1094
1228
 
1095
1229
  /* Wo weicht die Vorlage vom Bestand ab? Liefert je Zeile die Felder, die
1096
1230
  „Vorlage anwenden" aendern wuerde - die Beschriftung ausgenommen. */
package/admin/js/enums.js CHANGED
@@ -9,15 +9,58 @@ import { socket } from './verbindung.js';
9
9
  suchen haben. */
10
10
  export var enums = {};
11
11
 
12
+ /* Wissen wir gerade, was in den Aufzaehlungen steht?
13
+
14
+ Das ist keine Feinheit, sondern die Bedingung dafuer, ueberhaupt eine
15
+ schreiben zu duerfen. Frueher setzte ein Ladefehler `enums` auf `{}`
16
+ und verschwieg es. Danach hielt die Werkbank jede Aufzaehlung fuer
17
+ nicht vorhanden - der Katalog bot `garden` weiter an, weil der
18
+ Dublettenfilter gegen nichts pruefte, und `enumAenderungen` baute sie
19
+ als NEU aus der Vorlage. `setObject` ersetzt ein Objekt vollstaendig:
20
+ Gemessen 09.09.2026 am Testsystem verlor `enum.rooms.garden` dabei
21
+ drei Mitglieder und behielt eines, dazu die Herkunftsmarke, die ihn
22
+ spaeter auch noch loeschbar macht. Bei Ricardo waeren das die aus der
23
+ CCU gespiegelten Raeume.
24
+
25
+ Solange diese Marke steht, wird keine Aufzaehlung angefasst. */
26
+ var unbekannt = false;
27
+ export function enumsUnbekannt() { return unbekannt; }
28
+
29
+ /* Ein Aussetzer soll die Werkbank nicht fuer den Rest der Sitzung
30
+ lahmlegen. Dreimal mit wachsendem Abstand, dann ist Schluss - eine
31
+ Schleife ohne Ende waere schlimmer als der Fehler. */
32
+ var versuch = 0;
33
+ var wiederTimer = null;
34
+
12
35
  export function ladeEnums(danach) {
36
+ if (wiederTimer) { clearTimeout(wiederTimer); wiederTimer = null; }
13
37
  socket.emit('getObjectView', 'system', 'enum',
14
- { startkey: 'enum.', endkey: 'enum.\u9999' }, function (err, doc) {
15
- enums = {};
16
- if (!err && doc && doc.rows) {
17
- doc.rows.forEach(function (r) {
18
- if (r.value && r.id) { enums[r.id] = r.value; }
19
- });
38
+ { startkey: 'enum.', endkey: 'enum.香' }, function (err, doc) {
39
+ if (err || !doc || !doc.rows) {
40
+ /* Den alten Stand behalten. Er ist vielleicht veraltet, aber
41
+ veraltet ist naeher an der Wahrheit als leer. */
42
+ unbekannt = true;
43
+ console.warn('[alias-workbench] Aufzaehlungen nicht geladen: ' +
44
+ (err || 'keine Antwort'));
45
+ if (versuch < 3) {
46
+ versuch++;
47
+ wiederTimer = setTimeout(function () {
48
+ wiederTimer = null;
49
+ ladeEnums();
50
+ }, versuch * 4000);
51
+ }
52
+ if (typeof danach === 'function') { danach(err || 'leer'); }
53
+ return;
20
54
  }
21
- if (typeof danach === 'function') { danach(); }
55
+ /* Erst sammeln, dann tauschen: ein Fehler mittendrin liesse sonst
56
+ einen halben Vorrat stehen. */
57
+ var frisch = {};
58
+ doc.rows.forEach(function (r) {
59
+ if (r.value && r.id) { frisch[r.id] = r.value; }
60
+ });
61
+ enums = frisch;
62
+ unbekannt = false;
63
+ versuch = 0;
64
+ if (typeof danach === 'function') { danach(null); }
22
65
  });
23
66
  }
@@ -79,6 +79,14 @@ var warumAuf = false;
79
79
  var werteTimer = null;
80
80
  function werteGezeichnet() {
81
81
  werteTimer = null;
82
+ /* Im Vorlagenmodus steht rechts das Vorlagenblatt, nicht die
83
+ Zustandsliste — ein eintreffender Wert hat dort nichts zu melden.
84
+ `setzeModus('vorlagen')` laesst `S.current` ausdruecklich stehen,
85
+ damit man seine Auswahl beim Zurueckwechseln wiederfindet; ohne
86
+ diese Zeile baute jeder Takt eines Solarzaehlers das ganze Blatt
87
+ neu, samt `probelauf` ueber alle Kandidaten und alle Vorlagen
88
+ (gemessen 09.09.2026: vier Werte, vier Neuaufbauten). */
89
+ if (S.baumModus === 'vorlagen') { return; }
82
90
  if (document.querySelector('dialog[open]')) { return; }
83
91
  if (tipptGerade()) { werteTimer = setTimeout(werteGezeichnet, 700); return; }
84
92
  if (S.current) { zeichneErgebnis(); }
@@ -394,15 +402,29 @@ var warumAuf = false;
394
402
 
395
403
  `mousedown` statt `click`, wie bei den Vorschlagslisten der
396
404
  Zielleiste: der Mausdruck kommt vor dem Neuzeichnen. */
405
+ /* Gefragt wird das Dokument, nicht die Erinnerung.
406
+
407
+ Die Liste haengt am `body`, damit sie ein automatisches
408
+ Neuzeichnen ueberlebt. Der Knopf aber ist danach ein anderer,
409
+ und mit ihm diese Merkstelle: sie stand wieder auf „keine",
410
+ waehrend die alte Liste weiter am Body hing. Der zweite Klick
411
+ legte eine zweite Liste ueber die erste, und der Zuhoerer am
412
+ Body raeumte nicht auf, weil der Knopf das Ereignis abfaengt
413
+ (gemessen 09.09.2026). */
414
+ var offeneListe = function () { return document.querySelector('.sprungliste'); };
397
415
  var listeQ = null;
398
416
  var zuQ = function () {
399
- if (listeQ && listeQ.parentNode) { listeQ.parentNode.removeChild(listeQ); }
417
+ var da = offeneListe();
418
+ while (da) {
419
+ if (da.parentNode) { da.parentNode.removeChild(da); }
420
+ da = offeneListe();
421
+ }
400
422
  listeQ = null;
401
423
  };
402
424
  sprung.addEventListener('mousedown', function (ev) {
403
425
  ev.preventDefault();
404
426
  ev.stopPropagation();
405
- if (listeQ) { zuQ(); return; }
427
+ if (offeneListe()) { zuQ(); return; }
406
428
  listeQ = el('div', 'vorschlaege sprungliste');
407
429
  listeQ.appendChild(el('div', 'vzkopf', tr('result.pickSource')));
408
430
  verteilung.forEach(function (q) {
@@ -665,8 +687,20 @@ var warumAuf = false;
665
687
  var gid = S.objects[vorh].native.vorlage;
666
688
  if (S.VORLAGEN.some(function (x) { return x.id === gid; })) { fest = gid; }
667
689
  }
668
- var neuE = rohJetzt ? alleUndVorlage(S.current, fest)
669
- : vorschlag(S.current, fest);
690
+ /* Ausgang und Handwahl gehoeren mit.
691
+
692
+ Ohne die Instanz kamen an einer Mehrfachvorlage die Zeilen von
693
+ Ausgang 1 zurueck, waehrend das Ziel weiter `…Ausgang_2` hiess.
694
+ Und `fest` allein reicht nicht: Es ist nur gesetzt, wenn schon
695
+ ein Alias mit Vorlage dasteht. Hatte die Automatik nichts
696
+ gefunden und man die Vorlage von Hand gewaehlt, suchte der
697
+ Neuaufbau wieder automatisch, fand nichts — und man landete im
698
+ Rohmodus, die Handwahl weg (gemessen 09.09.2026). Der
699
+ Ausgangswechsel eine Stelle weiter oben macht es laengst
700
+ richtig. */
701
+ var vid = fest || e.vorlage || null;
702
+ var neuE = rohJetzt ? alleUndVorlage(S.current, vid, e.instanz)
703
+ : vorschlag(S.current, vid, e.instanz);
670
704
  if (!neuE) { return; }
671
705
  neuE.roh = rohJetzt;
672
706
  neuE.vorschlag = true;
@@ -721,8 +755,11 @@ var warumAuf = false;
721
755
  der langen Erklaerung. Sie ist weg: dieselbe Auskunft haengt jetzt
722
756
  am Hinweis der Legende, und die steht dort, wo die getoenten Zeilen
723
757
  stehen. Zweimal dasselbe, einmal oben und einmal ganz unten, war
724
- eine zu viel (Ricardo, 08.09.2026). Die Texte `info.explain` und
725
- `info.notPartOf` bleiben in Gebrauch — die Legende nutzt sie. */
758
+ eine zu viel (Ricardo, 08.09.2026). Der Text `info.explain` bleibt in
759
+ Gebrauch — die Legende nutzt ihn. `info.notPartOf` dagegen rief nach
760
+ dem Umbau niemand mehr; dieser Satz behauptete das Gegenteil, und
761
+ der Schluessel stand noch ein Jahr in beiden Sprachdateien, weil der
762
+ Test nur die Richtung Code → JSON prueft (09.09.2026 entfernt). */
726
763
 
727
764
  /* Was sich gegenueber dem gespeicherten Stand aendern wuerde. */
728
765
  function baueDiffKarte(host, e) {
@@ -754,7 +791,7 @@ var warumAuf = false;
754
791
 
755
792
  /* Pruefungen, Fusszeile und die Knopfleiste unten - beschriftet
756
793
  nach dem, was der Klick tatsaechlich tut. */
757
- function setzeKnoepfe(e, haupt, pflichtFehlt, _infoNamen) {
794
+ function setzeKnoepfe(e, haupt, pflichtFehlt) {
758
795
  baueChecks(e, haupt, pflichtFehlt);
759
796
 
760
797
  /* Der Knopf soll sagen, was er tut — nicht, wie er es tut. */
@@ -885,19 +922,19 @@ var warumAuf = false;
885
922
  }
886
923
 
887
924
  var pl = berechnePlaetze(e, haupt);
888
- var imInfo = pl.imInfo;
925
+
889
926
 
890
927
  baueListe(host, e, pl, rateKnopf, musterBlock);
891
928
  rateKnopf = null;
892
929
  musterBlock = null;
893
930
 
894
- var infoNamen = Object.keys(imInfo);
931
+
895
932
 
896
933
  /* --- Änderungen gegenüber dem, was gespeichert ist --- */
897
934
  baueDiffKarte(host, e);
898
935
 
899
936
  /* --- Prüfungen, Fusszeile, Knopfleiste --- */
900
- setzeKnoepfe(e, haupt, pflichtFehlt, infoNamen);
937
+ setzeKnoepfe(e, haupt, pflichtFehlt);
901
938
  }
902
939
 
903
940
  /* Uebersicht ueber alle Geraete unterhalb eines Ordners. */
@@ -1,5 +1,5 @@
1
1
  import { holeText } from './basis.js';
2
- import { txt } from './sprache.js';
2
+ import { txt, alleTexte } from './sprache.js';
3
3
  import { einst } from './einstellungen.js';
4
4
  import './enums.js';
5
5
 
@@ -304,10 +304,19 @@ export function vorlageZu(id, art, name) {
304
304
  var v = null;
305
305
  liste.forEach(function (x) { if (!v && x._id === kurz) { v = x; } });
306
306
  if (v) { return v; }
307
- var n = String(name || '').toLowerCase();
307
+ var n = String(name || '').trim().toLowerCase();
308
308
  if (!n) { return null; }
309
+ /* Alle Sprachfassungen, nicht nur die eingestellte.
310
+
311
+ Im Bestand steht, was jemand getippt hat — „Light" etwa, waehrend
312
+ die Vorlage auf einem deutschen Admin „Licht" heisst. Verglichen
313
+ wurde aber nur die eine Fassung, und ohne Treffer entsteht beim
314
+ naechsten Schreiben ein Doppelgaenger `enum.functions.light` neben
315
+ der vorhandenen Funktion (gefunden 09.09.2026). Der Vergleich war
316
+ schon immer unabhaengig von Gross- und Kleinschreibung; was fehlte,
317
+ waren die anderen Sprachen. */
309
318
  liste.forEach(function (x) {
310
- if (!v && String(txt(x.name) || '').toLowerCase() === n) { v = x; }
319
+ if (!v && alleTexte(x.name).indexOf(n) !== -1) { v = x; }
311
320
  });
312
321
  return v;
313
322
  }