iobroker.alias-workbench 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,55 @@
4
4
  import { S } from './zustand.js';
5
5
  import { D } from './basis.js';
6
6
 
7
+ /* Der Ausdruck eines Platzes als blosser Text — ohne Schraegstriche.
8
+
9
+ Der Detector liefert seine Muster ueber `ChannelDetector.getPatterns()`
10
+ (so haengt `src/detector-bundle.js` sie an `window`), und diese Funktion
11
+ gibt `role` nicht als RegExp weiter, sondern als `role.toString()` —
12
+ also MIT den Schraegstrichen:
13
+
14
+ /^indicator(\.maintenance)?\.(lowbat|battery)$/
15
+
16
+ Das ist bei uns der Normalfall, nicht die Ausnahme: ein RegExp bekommen
17
+ wir nur, wenn jemand die Muster direkt aus `typePatterns` liest, etwa
18
+ ein Test. Wer bloss `.source` abfragt und sonst `String(r)` nimmt, haelt
19
+ die Schraegstriche fuer Teil des Ausdrucks und findet danach weder den
20
+ Anker `^` noch das Ende `$`.
21
+
22
+ Gefunden am 08.09.2026 am Produktivsystem, und der eine Fehler zog weit:
23
+ `ROLLEN` hatte 210 statt 232 Eintraege (E27) — `indicator.lowbat` fehlte,
24
+ die Rolle, die hm-rpc schreibt —, `BRIGHTNESS` bekam in sechs Mustern gar
25
+ keine Rolle (E6), `plaetzeFuerRollen` fand zu keiner einzigen Rolle einen
26
+ Platz, und die Abweichungskarte behauptete „haette ohnehin nicht
27
+ gezaehlt" ueber Punkte mit Platz. Die Node-Tests sahen nichts davon: sie
28
+ fuettern die Zerlegung mit echten RegExp-Literalen.
29
+
30
+ Deshalb geht seither jede Stelle, die einen Platzausdruck liest, hier
31
+ durch — es sind sieben. */
32
+ export function ausdruckText(r) {
33
+ if (!r) { return ''; }
34
+ if (r.source !== undefined) { return r.source; }
35
+ var q = String(r);
36
+ var m = /^\/(.*)\/[a-z]*$/.exec(q);
37
+ return m ? m[1] : q;
38
+ }
39
+
40
+ /* Der Ausdruck als RegExp — fuer alle, die damit pruefen wollen. Wirft
41
+ nicht: ein unbrauchbarer Ausdruck gibt `null`, und der Aufrufer
42
+ entscheidet, was das heisst. */
43
+ export function ausdruckRegExp(r) {
44
+ var q = ausdruckText(r);
45
+ if (!q) { return null; }
46
+ try { return new RegExp(q); } catch { return null; }
47
+ }
48
+
49
+ /* Passt eine Rolle auf den Ausdruck eines Platzes? */
50
+ export function rolleTrifft(r, rolle) {
51
+ if (!rolle) { return false; }
52
+ var re = ausdruckRegExp(r);
53
+ return re ? re.test(rolle) : false;
54
+ }
55
+
7
56
  /* Die Rollen kommen aus dem Paket, nicht aus einer eigenen Liste —
8
57
  sonst pflegt man eine zweite Wahrheit, die auseinanderlaeuft. */
9
58
  /* Manche Plaetze nennen keine Standardrolle, sondern nur einen
@@ -13,8 +62,8 @@ import { D } from './basis.js';
13
62
  liess sich beliebig oft hinzufuegen, und die Rolle fehlte auch in der
14
63
  Auswahlliste — der Platz war auf keinem Weg zu befuellen. */
15
64
  export function rolleAusAusdruck(r) {
16
- if (!r) { return ''; }
17
- var q = (r.source !== undefined) ? r.source : String(r);
65
+ var q = ausdruckText(r);
66
+ if (!q) { return ''; }
18
67
  if (q.charAt(0) !== '^' || q.slice(-1) !== '$') { return ''; }
19
68
  var t = q.slice(1, -1).split(String.fromCharCode(92)).join('');
20
69
  return /^[A-Za-z0-9_]+([.][A-Za-z0-9_]+)*$/.test(t) ? t : '';
@@ -41,8 +90,8 @@ export function rolleVonPlatz(st) { return st.defaultRole || rolleAusAusdruck(st
41
90
  dann bleibt es bei der Vorgaberolle. Lieber eine Schreibweise zu wenig
42
91
  als eine erfundene in der Liste. */
43
92
  export function schreibweisenAus(r) {
44
- if (!r) { return []; }
45
- var q = (r.source !== undefined) ? r.source : String(r);
93
+ var q = ausdruckText(r);
94
+ if (!q) { return []; }
46
95
  if (q.charAt(0) !== '^' || q.slice(-1) !== '$') { return []; }
47
96
  q = q.slice(1, -1);
48
97
 
@@ -160,7 +209,7 @@ export function plaetzeFuerRollen(musterName) {
160
209
  ROLLEN.forEach(function (r) {
161
210
  mu.states.forEach(function (st) {
162
211
  if (raus[r] || !st.role) { return; }
163
- try { if (new RegExp(st.role.source || st.role).test(r)) { raus[r] = st.name; } } catch { /* unbrauchbarer Ausdruck in der Vorlage: zaehlt nicht */ }
212
+ if (rolleTrifft(st.role, r)) { raus[r] = st.name; }
164
213
  });
165
214
  });
166
215
  return raus;
@@ -1,18 +1,33 @@
1
- /* Die uebersetzten Namen der Muster, angezapft beim Geraete-Adapter.
1
+ /* Die uebersetzten Namen der Muster, angezapft beim Admin.
2
2
 
3
- Der type-detector kennt nur rohe Typnamen (socket, thermostat). Der
4
- Geraete-Adapter fuehrt fuer genau diese Typen fertige Uebersetzungen
5
- in elf Sprachen - in seinem Bundle, ein Block je Sprache, und jeder
6
- Block gibt sich am Eintrag "type-Device type" selbst zu erkennen.
7
- Wir holen den Block der Oberflaechensprache und schlagen darin nach;
8
- eine eigene Liste wuerde bei jedem type-detector-Update altern
9
- (Ricardos Punkt 13, 25.08.2026 - Weg 2, wie der Raum/Funktion-Katalog).
3
+ Der type-detector kennt nur rohe Typnamen (socket, thermostat) und
4
+ bringt selbst KEINE Uebersetzung mit - er ist eine Bibliothek
5
+ (`@iobroker/type-detector`), kein Adapter. Uebersetzt werden seine
6
+ Typen nur in den Sprachdateien der Adapter, die ihn benutzen, und
7
+ jeder schleppt seine eigene Kopie mit. Ein Block je Sprache, jeder
8
+ gibt sich am Eintrag "type-Device type" selbst zu erkennen; wir holen
9
+ den der Oberflaechensprache und schlagen darin nach. Eine eigene
10
+ Liste wuerde bei jedem type-detector-Update altern (Ricardos Punkt 13,
11
+ 25.08.2026 - Weg 2, wie der Raum/Funktion-Katalog).
10
12
 
11
- Bricht die Kette - Adapter fehlt, Bundle umgebaut - bleibt die Karte
12
- leer und ueberall steht der rohe Name. Kein Fehler, nur weniger
13
- Komfort. Beide Bundle-Fassungen werden verstanden: die neue (4.x)
14
- schreibt Werte in Backticks, die alte (1.x, produktiv) als
15
- JSON-Strings. */
13
+ Gefragt wird ausschliesslich der ADMIN. Bis zum 08.09.2026 war es der
14
+ Geraete-Adapter; Ricardos Begruendung fuer den Wechsel: Der Admin ist
15
+ immer installiert, `devices` nicht, und er wird besser gepflegt. Was
16
+ er nicht uebersetzt, bleibt eben roh - gemessen am 08.09.2026:
17
+ Admin 8.0.11 kennt alle 53 Typen, Admin 7.8.23 nur 44.
18
+
19
+ Der Weg dorthin geht ueber zwei Verzeichnisse, weil keines allein
20
+ reicht: `index.html` nennt bei Admin 8 das richtige Buendel, bei
21
+ Admin 7 aber nur vier Bootstrap-Haeppchen, und die fuehren nicht
22
+ weiter (34 Dateien durchsucht, nichts gefunden). Dort steht die Karte
23
+ in einer Datei, die erst `mf-manifest.json` nennt - der Admin laedt
24
+ seine Oberflaeche ueber Module Federation. Also beide Listen
25
+ zusammen, kurze Dateien zuerst.
26
+
27
+ Bricht die Kette - Aufbau geaendert, Datei zu gross -, bleibt die
28
+ Karte leer und ueberall steht der rohe Name. Kein Fehler, nur weniger
29
+ Komfort. Beide Schreibweisen werden verstanden: Werte in Backticks
30
+ (Admin 8) und als JSON-Strings (Admin 7). */
16
31
 
17
32
  import { sprache } from './sprache.js';
18
33
  import { holeText } from './basis.js';
@@ -44,26 +59,76 @@ function ziehe(src) {
44
59
  return Object.keys(raus).length ? raus : null;
45
60
  }
46
61
 
62
+ /* Welche Dateien der Admin ueberhaupt ausliefert. Zwei Verzeichnisse,
63
+ zusammengelegt und ohne Doppel - siehe Kopf: keines allein reicht
64
+ fuer beide Admin-Fassungen. */
65
+ function adminDateien() {
66
+ var raus = [];
67
+ function dazu(f) {
68
+ /* Fuehrendes `./` weg, sonst zaehlt dieselbe Datei zweimal: das
69
+ Manifest schreibt `assets/lib-….js`, das index.html
70
+ `./assets/lib-….js`. Ohne dieses Abschneiden stuenden 30 Eintraege
71
+ in einer Liste, die nach 25 abgeschnitten wird - und die gesuchte
72
+ Datei faellt genauso heraus wie damals bei der Sortierung nach
73
+ Namenslaenge. */
74
+ f = f.replace(/^\.\//, '');
75
+ if (raus.indexOf(f) === -1) { raus.push(f); }
76
+ }
77
+ /* Gesucht werden PFADE, keine blossen Dateinamen.
78
+
79
+ Beide Verzeichnisse schreiben den Ordner dazu, wo einer gehoert:
80
+ `"sync":["assets/lib-DSjRYjfP.js"]` im Manifest, `assets/…js` im
81
+ index.html. Wer nur den Dateinamen aufsammelt und stur `assets/`
82
+ davorsetzt, erfindet zwei Pfade, die es nicht gibt: das Manifest
83
+ nennt unter `remoteEntry` und `ssrRemoteEntry` je einen Namen mit
84
+ ausdruecklich leerem `"path":""` - die zwei Dateien liegen unter `/`.
85
+ Das gab beim Laden zwei 404 in der Konsole (A1, gefunden 08.09.2026
86
+ produktiv). Geholfen haben sie ohnehin nie: hinter remoteEntry steckt
87
+ der Einstieg von Module Federation, nicht die Sprachkarte.
88
+
89
+ Der Schraegstrich im Muster ist zugleich die Bedingung - ein nackter
90
+ Name kommt so gar nicht erst in die Liste, und ein Admin, der seine
91
+ Buendel eines Tages woanders ablegt, wird trotzdem gefunden. */
92
+ var pfade = /[A-Za-z0-9_\-.]+\/[A-Za-z0-9_\-./]+\.js/g;
93
+ return holeText('/mf-manifest.json')['catch'](function () { return ''; })
94
+ .then(function (mf) {
95
+ (mf.match(pfade) || []).forEach(dazu);
96
+ return holeText('/index.html')['catch'](function () { return ''; });
97
+ })
98
+ .then(function (html) {
99
+ (html.match(pfade) || []).forEach(dazu);
100
+ /* Reihenfolge bleibt, wie die Verzeichnisse sie nennen. Erst
101
+ wurde nach Namenslaenge sortiert - uebernommen vom
102
+ Aufzaehlungskatalog, wo es das Riesenbuendel nach hinten
103
+ schiebt. Hier war es genau falsch: der Name der gesuchten Datei
104
+ ist bei Admin 8 mit 143 Zeichen der laengste ueberhaupt, sie
105
+ rutschte ans Ende und fiel aus der Auswahl. Ungeordnet steht
106
+ sie an fuenfter Stelle von 21, bei Admin 7 an vierter von 19 -
107
+ beide Verzeichnisse nennen das Wichtige zuerst (gemessen
108
+ 08.09.2026). Das Groessenlimit unten haelt die dicken Buendel
109
+ ohnehin heraus. */
110
+ return raus.slice(0, 25);
111
+ });
112
+ }
113
+
47
114
  export function ladeMusterNamen(nachher) {
48
115
  if (geholt) { return; }
49
116
  geholt = true;
50
- holeText('/adapter/devices/tab.html').then(function (html) {
51
- /* Die tab.html nennt mehrere Bausteine (Bootstrap-Haeppchen,
52
- Vendor, Hauptbundle). Die Karte steckt im Hauptbundle - das
53
- heisst in beiden bekannten Fassungen index-<hash>.js, also
54
- dieses zuerst, die uebrigen als Rueckfall der Reihe nach. */
55
- var dateien = (html.match(/assets\/[A-Za-z0-9_\-.]+\.js/g) || [])
56
- .filter(function (x, i, a) { return a.indexOf(x) === i; })
57
- .sort(function (a, b) {
58
- return (b.indexOf('assets/index-') === 0 ? 1 : 0)
59
- - (a.indexOf('assets/index-') === 0 ? 1 : 0);
60
- })
61
- .slice(0, 8);
117
+ adminDateien().then(function (dateien) {
62
118
  if (!dateien.length) { throw new Error('kein Bundle'); }
63
119
  return dateien.reduce(function (kette, f) {
64
120
  return kette.then(function (fertig) {
65
121
  if (fertig) { return true; }
66
- return holeText('/adapter/devices/' + f).then(function (src) {
122
+ return holeText('/' + f).then(function (src) {
123
+ /* Die Grenze liegt hoeher als beim Aufzaehlungskatalog (3 MB):
124
+ bei Admin 8 steckt die Karte ausgerechnet im grossen
125
+ GUI-Buendel, gemessen 8,4 MB (08.09.2026). Mit der alten
126
+ Grenze wurde genau diese Datei uebersprungen und produktiv
127
+ blieb jeder Beiname roh. Teuer ist es nicht: derselbe Admin
128
+ hat das Buendel gerade selbst geladen, der zweite Griff
129
+ kommt aus dem Zwischenspeicher des Browsers. Alles darueber
130
+ ist kein Buendel mehr, sondern ein Versehen. */
131
+ if (src.length > 12000000) { return false; }
67
132
  var karte = ziehe(src);
68
133
  if (karte) { musterNamen = karte; return true; }
69
134
  return false;
@@ -23,7 +23,7 @@ import { tr, txt } from './sprache.js';
23
23
  import { socket } from './verbindung.js';
24
24
  import { aliasQuellen, kindZustaende } from './werte.js';
25
25
  import { holeZweig, indexNeu } from './objekte.js';
26
- import { waehle } from './entwurf.js';
26
+ import { waehle, knotenDa } from './entwurf.js';
27
27
  import { zeichneErgebnis } from './ergebnis.js';
28
28
 
29
29
  /* Die Punkte des Alias, die ueberhaupt eine Quelle haben. */
@@ -325,7 +325,7 @@ export function zeigeTausch() {
325
325
  /* Wie bei zeigeVerlegen und zeigeLoeschen: an der Quelle ist
326
326
  S.current die Quelle, gemeint ist das Ziel des Entwurfs (T1). */
327
327
  var ziel = (S.entwurf && (S.entwurf.ziel || S.entwurf.kanal)) || S.current;
328
- if (!ziel || ziel.indexOf('alias.') !== 0 || !S.objects[ziel]) { return; }
328
+ if (!ziel || ziel.indexOf('alias.') !== 0 || !knotenDa(ziel)) { return; }
329
329
  S.tauschZiel = ziel;
330
330
  S.tauschNeu = '';
331
331
  S.tauschWeg = {};
@@ -19,7 +19,8 @@ import {
19
19
  gemeinsameKanaele,
20
20
  kennungtauglich,
21
21
  entwurfFuer,
22
- aliasFuer
22
+ aliasFuer,
23
+ knotenDa
23
24
  } from './entwurf.js';
24
25
  import { zeichneErgebnis } from './ergebnis.js';
25
26
 
@@ -163,8 +164,22 @@ export function objektText(o, geordnet) {
163
164
  return JSON.stringify(geordnet ? ordne(k, '') : k, null, 2);
164
165
  }
165
166
 
167
+ /* Der gespeicherte Stand zu einer Kennung.
168
+
169
+ Aufzaehlungen liegen nicht in `S.objects` - die Werkbank haelt sie in
170
+ einem eigenen Vorrat, weil der Admin sie getrennt ausliefert. Der
171
+ Trockenlauf las trotzdem nur `S.objects`, und so blieb die Spalte
172
+ „bisher" bei jeder Aufzaehlung leer: verglichen wurde gegen nichts,
173
+ also stand rechts jede Zeile als neu da - auch die zehn Mitglieder,
174
+ die laengst drin waren (Ricardo, 08.09.2026). */
175
+ export function bestandsObjekt(id) {
176
+ if (S.objects[id]) { return S.objects[id]; }
177
+ if (String(id).indexOf('enum.') === 0 && enums[id]) { return enums[id]; }
178
+ return null;
179
+ }
180
+
166
181
  export function unveraendert(id, obj) {
167
- var alt = S.objects[id];
182
+ var alt = bestandsObjekt(id);
168
183
  if (!alt) { return false; }
169
184
  var putz = function (x) {
170
185
  var k = JSON.parse(JSON.stringify(x || {}));
@@ -301,7 +316,9 @@ export function baueObjekte(e) {
301
316
  das wird nicht ungefragt geloescht. */
302
317
  export function verwaiste(e) {
303
318
  var ziel = e.ziel || e.kanal;
304
- if (!S.objects[ziel]) { return []; }
319
+ /* Auch ohne Kanalobjekt kann darunter ein Alias liegen - dann gibt es
320
+ dort auch verwaiste Punkte zu finden. */
321
+ if (!knotenDa(ziel)) { return []; }
305
322
  var behalten = {};
306
323
  e.states.forEach(function (st) {
307
324
  if (st.on && st.n) { behalten[ziel + '.' + st.n] = 1; }
@@ -473,7 +490,8 @@ export function zeigeTrockenlauf(alleAusgaenge) {
473
490
  verspraeche er eine Wirkung, die er nicht hat. */
474
491
  var vergleichbar = liste.filter(function (x) { return !x.neu && !x.gleich; });
475
492
  var rohAnders = vergleichbar.some(function (x) {
476
- return objektText(S.objects[x.id], false) !== objektText(S.objects[x.id], true)
493
+ var b0 = bestandsObjekt(x.id);
494
+ return objektText(b0, false) !== objektText(b0, true)
477
495
  || objektText(x.obj, false) !== objektText(x.obj, true);
478
496
  });
479
497
  if (vergleichbar.length && rohAnders) {
@@ -672,7 +690,7 @@ export function zeigeTrockenlauf(alleAusgaenge) {
672
690
  var vgl = el('div', 'dvgl');
673
691
  vgl.appendChild(el('span', 'dkopf', tr('write.diffOld')));
674
692
  vgl.appendChild(el('span', 'dkopf', tr('write.diffNew')));
675
- zeilenVergleich(objektText(S.objects[x.id], !S.trockenRoh),
693
+ zeilenVergleich(objektText(bestandsObjekt(x.id), !S.trockenRoh),
676
694
  objektText(x.obj, !S.trockenRoh))
677
695
  .forEach(function (z) {
678
696
  vgl.appendChild(el('span', 'dz l ' + z.art, z.l === undefined ? '' : z.l));
@@ -693,7 +711,7 @@ export function zeigeTrockenlauf(alleAusgaenge) {
693
711
  var zt = $('#dry-titel');
694
712
  if (zt) {
695
713
  var zielName = S.entwurf.ziel || S.entwurf.kanal;
696
- zt.textContent = (S.objects[zielName] ? tr('write.dryTitleUpdate') : tr('write.dryTitleCreate')) +
714
+ zt.textContent = (knotenDa(zielName) ? tr('write.dryTitleUpdate') : tr('write.dryTitleCreate')) +
697
715
  (entwuerfe.length > 1 ? tr('write.dryOutputs', entwuerfe.length) : '');
698
716
  }
699
717
  /* Beim Oeffnen wieder herstellen: der Dialog wird wiederverwendet, und
@@ -729,7 +747,7 @@ function geschwisterVon(ziel) {
729
747
 
730
748
  export function zeigeLoeschen() {
731
749
  var ziel = (S.entwurf && (S.entwurf.ziel || S.entwurf.kanal)) || S.current;
732
- if (!ziel || ziel.indexOf('alias.') !== 0 || !S.objects[ziel]) { return; }
750
+ if (!ziel || ziel.indexOf('alias.') !== 0 || !knotenDa(ziel)) { return; }
733
751
  S.loeschZiel = ziel;
734
752
  S.loeschAlleAusgaenge = false;
735
753
  zeichneLoeschen();
@@ -110,6 +110,9 @@ export function beschrifteHtml() {
110
110
  setz('#tpl-titel', tr('tpls.title', ''));
111
111
  setz('#btn-tpl-save', tr('tpls.saveNew'));
112
112
  setz('#mqtt-titel', tr('mq.dialogTitle'));
113
+ /* Der Titel des Info-Dialogs traegt den Musternamen und wird beim
114
+ Oeffnen gesetzt; hier steht nur der Rueckfall ohne Muster. */
115
+ setz('#info-titel', tr('info.dlgTitle', ''));
113
116
  setz('#guess-titel', tr('guess.tplTitle'));
114
117
  setz('#btn-guess-go', tr('guess.tplGo'));
115
118
  setz('#tree .empty', tr('tree.loading'));
@@ -12,7 +12,7 @@ import { tasmotaBefehle } from './mqtt.js';
12
12
  import { socket } from './verbindung.js';
13
13
 
14
14
  import { tr, sprachtext } from './sprache.js';
15
- import { musterVon } from './erkennung.js';
15
+ import { musterVon, rolleTrifft } from './erkennung.js';
16
16
 
17
17
  /* Welche Instanz — der Reiter wird mit ?<instanz>&noFooter geladen. */
18
18
  var INSTANZ = (function () {
@@ -29,6 +29,24 @@ export function zeigeObjektzahl() {
29
29
  if (z) { z.textContent = tr('app.objectsCount', S.keysSorted.length); }
30
30
  }
31
31
 
32
+ /* Wie viele Wertabos laufen gerade? Steht neben der Objektzahl.
33
+
34
+ Nicht aus Neugier: die Abos werden beim Wechsel des Knotens abgemeldet
35
+ und neu gesetzt, und ob dabei wirklich alle wieder verschwinden, sieht
36
+ man sonst nirgends. Bleibt die Zahl beim Durchklicken stabil, stimmt
37
+ es; waechst sie, sammelt sich etwas an (Ricardo, 08.09.2026). */
38
+ export function zeigeAbozahl() {
39
+ var z = $('#meta-abos');
40
+ if (!z) { return; }
41
+ var n = (S.abo || []).length;
42
+ z.textContent = ' \u00b7 ' + (n === 1 ? tr('app.subsCount1') : tr('app.subsCount', n));
43
+ /* Die Muster gehoeren dazu, sonst ist die Zahl ein Raetsel: „2 Abos"
44
+ bei zwanzig sichtbaren Werten liest sich falsch, bis man sieht, dass
45
+ ein Muster einen ganzen Zweig deckt (Ricardo, 08.09.2026). */
46
+ z.title = tr('app.subsHint') + String.fromCharCode(10) +
47
+ (S.abo || []).map(function (mu) { return ' ' + mu; }).join(String.fromCharCode(10));
48
+ }
49
+
32
50
  export function setzeMeta() {
33
51
  var m = $('#meta');
34
52
  if (!m) { return; }
@@ -41,6 +59,8 @@ export function setzeMeta() {
41
59
  steht sie neben ihresgleichen; ein eigener Span, damit das Abo sie
42
60
  nachfuehren kann, ohne die ganze Zeile neu zu bauen. */
43
61
  m.appendChild(el('span', null, tr('app.objectsCount', S.keysSorted.length))).id = 'meta-objekte';
62
+ m.appendChild(el('span', null, '')).id = 'meta-abos';
63
+ zeigeAbozahl();
44
64
  if (S.vorlagenUnvollstaendig) {
45
65
  var w = el('span', 'chip bad', tr('tpls.loadFailed'));
46
66
  w.style.marginLeft = '8px';
@@ -559,7 +579,7 @@ export function anMusterAnpassen(e, neuTyp) {
559
579
  var s = null;
560
580
  muster.states.forEach(function (st) {
561
581
  if (s || !st.role) { return; }
562
- try { if (new RegExp(st.role.source || st.role).test(rolle)) { s = st; } } catch { /* dito: eine kaputte Rolle macht die Vorlage nicht passend */ }
582
+ if (rolleTrifft(st.role, rolle)) { s = st; } /* eine kaputte Rolle macht die Vorlage nicht passend */
563
583
  });
564
584
  return s;
565
585
  }
@@ -9,7 +9,7 @@ import { D, $, el, kurz } from './basis.js';
9
9
  import { tr, sprachtext, sprache } from './sprache.js';
10
10
  import { katalogFehlt } from './katalog.js';
11
11
  import { funktionsAuswahl, kennungZuFunktion } from './aufzaehlungen.js';
12
- import { musterVon } from './erkennung.js';
12
+ import { musterVon, rolleTrifft } from './erkennung.js';
13
13
  import { musterZeile, musterName } from './musternamen.js';
14
14
  import { ladeVorlagen, aendereVorlagen, setzeMeta, pruefeVorlage, INSTANZ_ID } from './vorlagen.js';
15
15
  import { kindZustaende, hatPunkt, feldAusFormel } from './werte.js';
@@ -551,8 +551,7 @@ function rollenBlock(rolle, geraetetyp, setzen) {
551
551
  if (must) {
552
552
  var treffer = null;
553
553
  must.states.forEach(function (pp) {
554
- if (!treffer && pp.role && rolle &&
555
- new RegExp(pp.role.source ? pp.role.source : pp.role).test(rolle)) { treffer = pp; }
554
+ if (!treffer && pp.role && rolleTrifft(pp.role, rolle)) { treffer = pp; }
556
555
  });
557
556
  var hin = el('div', 'sugg');
558
557
  if (treffer) {
package/admin/js/werte.js CHANGED
@@ -93,6 +93,26 @@ export function holeWerte(kanal, extraIds, fertig) {
93
93
  });
94
94
  }
95
95
 
96
+ /* Nur diese Kennungen, ohne den Rundumschlag ueber einen Kanal.
97
+
98
+ Gebraucht dort, wo Zeilen erst spaeter dazukommen und ihre Quelle
99
+ ausserhalb des angeklickten Knotens liegt - dann ist der Kanal
100
+ laengst abgefragt, und ein zweites `getForeignStates` darauf braechte
101
+ nichts als Last. */
102
+ export function holeEinzelne(ids, fertig) {
103
+ var eindeutig = {};
104
+ (ids || []).forEach(function (i) { if (i) { eindeutig[i] = 1; } });
105
+ var liste = Object.keys(eindeutig);
106
+ if (!liste.length) { if (typeof fertig === 'function') { fertig(false); } return; }
107
+ var offen = liste.length, etwas = false;
108
+ liste.forEach(function (id) {
109
+ socket.emit('getState', id, function (err, st) {
110
+ if (!err && st) { S.werte[id] = st; etwas = true; }
111
+ if (--offen <= 0 && typeof fertig === 'function') { fertig(etwas); }
112
+ });
113
+ });
114
+ }
115
+
96
116
  /* ================== Formel und Wert ================== */
97
117
  export function auswerten(formel, roh) {
98
118
  if (roh === undefined || roh === null) { return { ok: false, txt: tr('detail.noValue') }; }
@@ -11,7 +11,7 @@ import { el } from './basis.js';
11
11
  import { tr } from './sprache.js';
12
12
  import { enumListe } from './aufzaehlungen.js';
13
13
  import { enumZeile, setzeZiel } from './zuordnung.js';
14
- import { aliasFuer, ordnerUnterAlias, kennungtauglich } from './entwurf.js';
14
+ import { aliasFuer, ordnerUnterAlias, kennungtauglich, knotenDa } from './entwurf.js';
15
15
  import { zeichneErgebnis, entwurfAngefasst, angebotFrisch } from './ergebnis.js';
16
16
  import { zeigeVerlegen } from './schreiben.js';
17
17
 
@@ -303,7 +303,10 @@ export function baueZielleiste(host, e) {
303
303
  /* Beide Faelle, an einer Stelle. Vorher stand „wird neu angelegt"
304
304
  oben im Kopf und „gibt es schon" hier — zwei Formulierungen fuer
305
305
  dieselbe Auskunft, an zwei Orten. */
306
- var gibtsZiel = !!S.objects[e.ziel];
306
+ /* `knotenDa`, nicht `S.objects`: ein Alias, dessen Punkte von Hand
307
+ angelegt wurden, hat oft kein Kanalobjekt - der Chip meldete dort
308
+ „wird neu angelegt", obwohl geaendert wird (Ricardo, 08.09.2026). */
309
+ var gibtsZiel = knotenDa(e.ziel);
307
310
  var ch2 = el('span', 'chip ' + (gibtsZiel ? 'warn' : 'ok'),
308
311
  gibtsZiel ? tr('target.exists') : tr('result.newlyCreated'));
309
312
  ch2.style.marginLeft = '7px';