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.
@@ -14,13 +14,13 @@ import {
14
14
  } from './aufzaehlungen.js';
15
15
  import { enumVorlagen, katalogFehlt, ikonCache, ikonErlaubt, ikonNachtrag,
16
16
  ikonTaugt, vorlageZu, holeIkon } from './katalog.js';
17
- import { kindZustaende, aliasQuellen } from './werte.js';
18
- import { aliasFuer, zielId } from './entwurf.js';
17
+ import { kindZustaende, aliasQuellen, holeEinzelne } from './werte.js';
18
+ import { aliasFuer, zielId, knotenDa } from './entwurf.js';
19
19
  import { zeichneErgebnis, entwurfAngefasst, angebotFrisch } from './ergebnis.js';
20
20
 
21
21
 
22
22
  import { ausgangName } from './vorlagen.js';
23
- import { musterVon } from './erkennung.js';
23
+ import { musterVon, rolleTrifft } from './erkennung.js';
24
24
 
25
25
  /* Welche Aufzaehlungen aendern sich, wenn dieser Entwurf geschrieben wird?
26
26
 
@@ -394,7 +394,11 @@ export function enumZeile(host, e) {
394
394
  export function setzeEnums(e) {
395
395
  if (e.raum !== undefined && e.funktion !== undefined) { return; }
396
396
  var ziel = e.ziel || e.kanal;
397
- var da = !!S.objects[ziel];
397
+ /* Gibt es den Alias, gelten SEINE Aufzaehlungen - auch wenn sein Kanal
398
+ kein eigenes Objekt hat. Sonst wurden Raum und Funktion geraten und
399
+ als „vorgeschlagen" angezeigt, obwohl beide laengst zugeordnet
400
+ waren (Ricardo, 08.09.2026). */
401
+ var da = knotenDa(ziel);
398
402
 
399
403
  if (e.raum === undefined) {
400
404
  var r = da ? (enumsVon(ziel, 'rooms')[0] || '') : '';
@@ -499,7 +503,21 @@ export function setzeZiel(e) {
499
503
  export function uebernehmeBestand(e) {
500
504
  if (!e.ziel || e.bestandGeprueft === e.ziel) { return; }
501
505
  e.bestandGeprueft = e.ziel;
502
- if (!S.objects[e.ziel]) { return; }
506
+ /* Gefragt ist, ob es unter dem Ziel schon einen Alias gibt - nicht, ob
507
+ das Ziel selbst ein Objekt ist. Das ist nicht dasselbe: wer seine
508
+ Punkte von Hand im Admin anlegt, bekommt `alias.0.X.Y.PUNKT`, ohne
509
+ dass `alias.0.X.Y` als Kanal entsteht. Der Baum zeigt so einen
510
+ Knoten trotzdem, weil er sich aus den Kennungen aufbaut - `knotenDa`
511
+ ist genau dafuer da.
512
+
513
+ Mit der alten Frage stieg der ganze Bestandsabgleich dort aus:
514
+ `alias.0.Bastelzimmer.Klima` und `alias.0.Solar.Einstellungen.Bilanz`
515
+ haben kein Kanalobjekt, und ein Aktualisieren haette ihre Namen
516
+ durch die blossen Punktnamen ersetzt - „Strompreis fuer die
517
+ Ersparnisrechnung" waere zu „Strompreis_ct_kWh" geworden. Rollen,
518
+ Formeln und Schreibrichtung waeren ebenso durchgefallen
519
+ (Ricardo, 08.09.2026). */
520
+ if (!knotenDa(e.ziel)) { return; }
503
521
  /* Im Aliasmodus ist der Entwurf schon der Bestand. */
504
522
  if (e.kanal === e.ziel) { return; }
505
523
 
@@ -521,21 +539,80 @@ export function uebernehmeBestand(e) {
521
539
  Quelle zeigte `thermostat` statt `airCondition`, die Schreibquelle
522
540
  war weg, und von neun Punkten blieben zwei angehakt.
523
541
 
524
- Nur ohne Vorlage. Hat eine gegriffen, ist ihr Vorschlag gemeint -
525
- sonst uebertoente der Bestand sie stillschweigend, und „aendert
526
- sich" haette nichts mehr zu melden. */
542
+ Auch mit Vorlage. Hier stand `if (e.vorlage) return;`, mit der
543
+ Begruendung, sonst uebertoene der Bestand die Vorlage stillschweigend
544
+ und „aendert sich" haette nichts mehr zu melden. Das war zweimal
545
+ falsch.
546
+
547
+ Erstens ist es nicht mehr stillschweigend: `vorlagenWert` haelt fest,
548
+ was die Vorlage wollte, die Zeile traegt dann „weicht von der Vorlage
549
+ ab", und der ←-Knopf holt jedes Feld einzeln zurueck. Genau so haelt
550
+ es die Zwillingsfunktion `bestandVorrang` seit jeher - sie uebernimmt
551
+ den Bestand auch bei gesetzter Vorlage.
552
+
553
+ Zweitens widersprachen sich damit die beiden Ansichten, und das ist
554
+ der Punkt, den U21 verlangt: derselbe Alias, derselbe Stand. Gemessen
555
+ am 08.09.2026 produktiv an `alias.0.Wohnkueche.Lavalampe`: sieben der
556
+ acht Punkte stimmten (sie liefen ueber `bestandVorrang`), ACTUAL
557
+ nicht. Der laeuft ueber den Umbenennungs-Zweig unten - die Vorlage
558
+ `tasmota-lampe` nennt den Punkt ON_ACTUAL, im Alias heisst er ACTUAL
559
+ - und blieb deshalb auf dem Vorlagenstand stehen: `sensor.light`
560
+ statt `state`, Beschriftung leer statt „Lavalampe ACTUAL". Ein
561
+ Aktualisieren aus der Quellenansicht haette beides ueberschrieben,
562
+ und die Marke nannte im Tooltip nur die Rolle, nicht den Namen.
563
+
564
+ `vorlagenWert` wird nur gesetzt, wo noch keiner steht: fuer die
565
+ Zeilen, deren Name schon passt, hat `bestandVorrang` ihn vorher
566
+ gefuellt - mit dem echten Vorlagenstand. Ihn hier zu ueberschreiben
567
+ hiesse, dem Vorlagenknopf den Wert zu nehmen, den er zurueckholen
568
+ soll. */
527
569
  function vomAliasUebernehmen(z, o) {
528
- if (e.vorlage) { return; }
529
570
  var c = o.common || {}, a = c.alias || {}, q2 = aliasQuellen(o);
530
- if (c.role) { z.role = c.role; }
531
- if (c.type) { z.typ = c.type; }
571
+ if (!z.vorlagenWert) {
572
+ z.vorlagenWert = {
573
+ on: z.on, role: z.role, typ: z.typ, unit: z.unit, caption: z.caption,
574
+ f: z.f, fw: z.fw, srcR: z.srcR, srcW: z.srcW, states: z.states
575
+ };
576
+ }
577
+ /* `!== undefined`, nicht bloss wahr: ein Punkt mit leerer Rolle im
578
+ Alias hat sie bewusst leer, und die Zwillingsfunktion
579
+ `bestandVorrang` fragt genauso. Solange die beiden dieselbe Frage
580
+ stellen, kann der Stand nicht wieder auseinanderlaufen. */
581
+ if (c.role !== undefined) { z.role = c.role; }
582
+ if (c.type !== undefined) { z.typ = c.type; }
532
583
  z.unit = c.unit || '';
533
584
  z.states = c.states || undefined;
534
585
  z.f = (typeof a.read === 'string') ? a.read : '';
535
586
  z.fw = (typeof a.write === 'string') ? a.write : '';
587
+ /* Die Beschriftung steht im Alias und gehoert dem Nutzer.
588
+
589
+ Sie fehlte hier als einzige - `bestandVorrang` holt sie laengst
590
+ (`s.caption = txt(c.name)`), diese Zwillingsfunktion nicht. Also
591
+ blieb `caption` leer, im aufgeklappten Feld stand nichts, und ein
592
+ „Alias aktualisieren" haette den Namen durch den blossen
593
+ Zeilennamen ersetzt: aus „Netzbezug (+) / Einspeisung (-)" waere
594
+ „Bezug" geworden (Ricardo, 07.09.2026, an alias.0.Solar.Netz).
595
+ Gleicht die Beschriftung dem Zeilennamen, bleibt sie leer - so
596
+ haelt es der Zweig darunter, der neue Zeilen anlegt, auch. */
597
+ var nm2 = txt(c.name);
598
+ z.caption = (nm2 && nm2 !== z.n) ? nm2 : '';
536
599
  if (q2.read) { z.srcR = q2.read; }
600
+ /* Und dieselbe Schreibregel wie im Bestandsvorrang.
601
+
602
+ Hier stand die Schreibquelle nur fuer das getrennte Paar; sonst
603
+ blieb stehen, was der Vorschlag aus der Quelle abgeleitet hatte.
604
+ Bei einem Nur-Lese-Alias auf einen beschreibbaren Punkt hiess das:
605
+ die Quelle sagt `write: true`, der Alias sagt `write: false` - und
606
+ die Werkbank meldete eine Abweichung, die sie selbst erzeugt hatte,
607
+ und haette den Alias beim Aktualisieren schreibbar gemacht.
608
+
609
+ Belegt ist das Schreiben durch `common.write` oder durch eine
610
+ hinterlegte Schreibformel; bei getrennten Quellen zaehlt, was in
611
+ `alias.id.write` steht. */
612
+ z.srcW = q2.einfach
613
+ ? ((c.write === true || typeof a.write === 'string') ? (q2.write || '') : '')
614
+ : (q2.write || '');
537
615
  if (q2.write && !q2.einfach && q2.write !== q2.read) {
538
- z.srcW = q2.write;
539
616
  /* Die Gegenzeile eines Paars ist keine eigene mehr - ihre Quelle
540
617
  steckt jetzt als Schreibquelle in dieser Zeile. */
541
618
  e.states.forEach(function (x) {
@@ -605,7 +682,22 @@ export function uebernehmeBestand(e) {
605
682
  role: c.role || '', typ: c.type || '', unit: c.unit || '',
606
683
  states: c.states || undefined,
607
684
  wr: !!c.write,
608
- srcR: q.read || '', srcW: q.einfach ? '' : (q.write || ''),
685
+ srcR: q.read || '',
686
+ /* Dieselbe Schreibregel wie oben und im Bestandsvorrang.
687
+
688
+ Hier stand `q.einfach ? '' : (q.write || '')` - bei schlichtem
689
+ `alias.id` also immer leer, ohne `common.write` ueberhaupt
690
+ anzusehen. Es trifft jeden Alias, bei dem MEHRERE Punkte auf
691
+ denselben Quellpunkt zeigen: die eine Vorschlagszeile bekommt
692
+ der erste von ihnen, alle weiteren landen hier. Am
693
+ `alias.0.Badezimmer.Rollladen` zeigen OPEN, CLOSE, SET und pct
694
+ alle auf `…Bad.level`; CLOSE lief oben durch und behielt seine
695
+ Schreibquelle, OPEN, SET und pct verloren sie - ein
696
+ Aktualisieren haette den Rollladen ueber den Alias unfahrbar
697
+ gemacht (Ricardo, 08.09.2026). */
698
+ srcW: q.einfach
699
+ ? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
700
+ : (q.write || ''),
609
701
  f: (typeof a.read === 'string') ? a.read : '',
610
702
  fw: (typeof a.write === 'string') ? a.write : '',
611
703
  caption: (nm && nm !== n) ? nm : '',
@@ -627,6 +719,38 @@ export function uebernehmeBestand(e) {
627
719
  e.bestandNachgezeichnet = true;
628
720
  setTimeout(function () { if (S.entwurf === e) { zeichneErgebnis(); } }, 0);
629
721
  }
722
+
723
+ /* Und die Werte zu den eben ergaenzten Zeilen nachholen.
724
+
725
+ `waehle` holt die Werte, bevor es diese Zeilen ueberhaupt gibt: es
726
+ fragt den angeklickten Kanal ab und die Quellen, die der Entwurf zu
727
+ dem Zeitpunkt kennt. Was hier dazukommt, steht nur im Alias - und
728
+ seine Quelle kann irgendwo liegen. Solange sie unter demselben
729
+ Knoten liegt, faellt nichts auf, weil `getForeignStates` sie
730
+ mitgenommen hat. Liegt sie woanders, blieb die Zeile ohne Wert und
731
+ behauptete „Quelle liefert nichts", obwohl der Alias einwandfrei
732
+ arbeitet.
733
+
734
+ Gefunden am 07.09.2026 (Ricardo) an `alias.0.Solar.Netz`: drei
735
+ Punkte aus `0_userdata`, zwei aus `mqtt-client`. Nach einem
736
+ Reiterwechsel und dem ersten Klick auf den Knoten standen genau die
737
+ beiden mqtt-Zeilen leer; ein zweiter Klick heilte es, weil der Wert
738
+ dann schon im Vorrat lag. Der Mitschnitt zeigte, dass nach der
739
+ mqtt-Kennung nie gefragt wurde.
740
+
741
+ Einmal je Entwurf, und nur, wenn wirklich etwas fehlt. */
742
+ if (!e.bestandWerte) {
743
+ var fehlen = [];
744
+ e.states.forEach(function (s) {
745
+ if (s.srcR && !S.werte[s.srcR] && fehlen.indexOf(s.srcR) === -1) { fehlen.push(s.srcR); }
746
+ });
747
+ if (fehlen.length) {
748
+ e.bestandWerte = true;
749
+ holeEinzelne(fehlen, function (etwas) {
750
+ if (etwas && S.entwurf === e) { zeichneErgebnis(); }
751
+ });
752
+ }
753
+ }
630
754
  }
631
755
 
632
756
  export function unterschiede(e) {
@@ -636,7 +760,7 @@ export function unterschiede(e) {
636
760
  if (!mu || !rolle) { return false; }
637
761
  return mu.states.some(function (st) {
638
762
  if (!st.role) { return false; }
639
- try { return new RegExp(st.role.source || st.role).test(rolle); } catch { return false; }
763
+ return rolleTrifft(st.role, rolle);
640
764
  });
641
765
  }
642
766
  /* Was die Vorlage von sich aus vorgeschlagen hat. Ein Punkt, der
@@ -667,7 +791,7 @@ export function unterschiede(e) {
667
791
  Grundlinie — dann zaehlt, was sich gegenueber dem Gespeicherten
668
792
  aendert. Gibt es ihn noch nicht, ist es der Vorschlag der
669
793
  Vorlage. */
670
- var aliasDa = !!S.objects[zielBasis];
794
+ var aliasDa = knotenDa(zielBasis);
671
795
 
672
796
  e.states.forEach(function (s) {
673
797
  if (s.manuell) { raus.push({ n: s.n || '(ohne Namen)', was: tr('diff.newByHand') }); return; }
@@ -23,7 +23,10 @@ export var S = {
23
23
  baumModusVorher: 'quellen',
24
24
  aufgeklappt: {},
25
25
  alleKlapp: null,
26
- abo: null,
26
+ /* Die laufenden Wertabos, als Liste von Mustern. War einmal ein
27
+ einzelnes Muster - seit im Aliasmodus auch die Quellen abonniert
28
+ werden, sind es mehrere (08.09.2026). */
29
+ abo: [],
27
30
  trockenAlle: null,
28
31
  /* Vergleich im Trockenlauf: Felder geordnet (Vorgabe) oder in der
29
32
  Reihenfolge, in der sie wirklich im Speicher stehen. */
@@ -39,6 +42,9 @@ export var S = {
39
42
  loeschZiel: null,
40
43
  loeschAlleAusgaenge: false,
41
44
  verlegeZiel: null,
45
+ /* Einmal registrierter Schliesser fuer die Quellenauswahl am
46
+ Sprungknopf - siehe ergebnis.js. */
47
+ sprungZuhoerer: false,
42
48
  VORLAGEN: [],
43
49
  vorlagenUnvollstaendig: false,
44
50
  fokusOrdnerfeld: false,
@@ -7,11 +7,11 @@
7
7
  werden oben bei der Musterwahl gebaut und hier nur eingehaengt. */
8
8
 
9
9
  import { S } from './zustand.js';
10
- import { el } from './basis.js';
10
+ import { el, $ } from './basis.js';
11
11
  import { tr } from './sprache.js';
12
12
  import { erkenneEntwurf, musterVon, rolleVonPlatz } from './erkennung.js';
13
13
  import { kindZustaende, wertVon, fmt } from './werte.js';
14
- import { steuerKanaele, vorlagenAbweichung, bestandsAbweichung, waehle } from './entwurf.js';
14
+ import { steuerKanaele, vorlagenAbweichung, bestandsAbweichung, waehle, quelleHier, quellenVerteilung } from './entwurf.js';
15
15
  import { musterName } from './musternamen.js';
16
16
  import { detailZeile } from './detail.js';
17
17
  import { rateAnzahl, rateZurueck, rateZurueckEine, rateBehalten } from './vorschlagen.js';
@@ -97,7 +97,9 @@ export function berechnePlaetze(e, haupt) {
97
97
  - und die Vorbelegung machte es sofort wieder zunichte: die neun
98
98
  uebernommenen Zeilen hatten im thermostat-Muster keinen Platz und
99
99
  wurden abgewaehlt, uebrig blieben zwei. */
100
- var bestandDa = e.ziel && S.objects[e.ziel] && kindZustaende(e.ziel).length > 0;
100
+ var bestandDa = !!(e.ziel && kindZustaende(e.ziel).length > 0);
101
+
102
+
101
103
  if (!e.vorbelegt && !e.vorlage && S.current.indexOf('alias.') !== 0) {
102
104
  e.vorbelegt = true;
103
105
  if (bestandDa) {
@@ -133,6 +135,38 @@ export function berechnePlaetze(e, haupt) {
133
135
  }
134
136
 
135
137
  export function baueListe(host, e, pl, rateKnopf, musterBlock) {
138
+ /* Bezugspunkt der Marken: die Quelle, an der ich gerade STEHE.
139
+
140
+ Markiert wird, was nicht von hier kommt. Erst lag der Bezug beim
141
+ angeklickten Knoten - dann bekamen an einer Sparte (`…Licht.stat`)
142
+ alle Zeilen aus `…Licht.tele` eine Marke, obwohl es dasselbe Geraet
143
+ ist. Danach lag er bei der Hauptquelle - dann waren an
144
+ `…Stromzaehler.stat` genau die Zeilen markiert, die von DORT kamen,
145
+ also die falschen (Ricardo, 08.09.2026).
146
+
147
+ `quelleHier` loest beides: es sucht den Eintrag der Verteilung, zu
148
+ dem dieser Knoten gehoert - Sparten fallen darin schon zusammen. Am
149
+ Alias selbst steht man nirgends; dort gilt die Hauptquelle. Findet
150
+ sich gar kein Bezug (ein Sammelknoten weit ueber den Quellen), wird
151
+ nichts markiert - eine Marke ohne Standpunkt sagt nichts. */
152
+ var istAliasSicht = S.current.indexOf('alias.') === 0;
153
+ var aliasFuerMarke = istAliasSicht
154
+ ? S.current
155
+ : (e.ziel && kindZustaende(e.ziel).length ? e.ziel : '');
156
+ /* Am Alias steht man nirgends - dort tragen ALLE Zeilen ihren Tag,
157
+ sobald es mehrere Quellen gibt.
158
+
159
+ Vorher galt auch dort die Mehrheit, und nur die Minderheit war
160
+ markiert. Das ist willkuerlich: bei drei zu drei entscheidet, wessen
161
+ Punkt alphabetisch zuerst kommt, und ein Umbenennen kippt die
162
+ Markierung auf die andere Haelfte (Ricardo, 08.09.2026). Traegt
163
+ jede Zeile ihren Tag, gibt es keine Mehrheit mehr zu bestimmen. */
164
+ var vert = aliasFuerMarke ? quellenVerteilung(aliasFuerMarke) : [];
165
+ var alleTaggen = istAliasSicht && vert.length > 1;
166
+ var hauptQuelle = aliasFuerMarke
167
+ ? (istAliasSicht ? '' : (quelleHier(aliasFuerMarke, S.current) || ''))
168
+ : (e.kanal || '');
169
+
136
170
  var platzVon = pl.platzVon, platzAnzahl = pl.platzAnzahl;
137
171
 
138
172
  /* --- Gerätekarte ---
@@ -210,6 +244,7 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
210
244
  dazu haengt sich erst danach in die Leiste, weil vorher niemand
211
245
  weiss, ob es ueberhaupt eine braucht. */
212
246
  var ohnePlatzZahl = 0;
247
+ var ohnePlatzNamen = [];
213
248
  var an = e.states.filter(function (s) { return s.on; }).length;
214
249
  bar.appendChild(document.createTextNode(tr('list.willBeCreated')));
215
250
  bar.appendChild(el('span', 'cnt', tr('list.countOf', an, e.states.length)));
@@ -325,6 +360,16 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
325
360
  });
326
361
  card.appendChild(cap);
327
362
 
363
+ /* Platz fuer die Legende. Sie steht zwischen Kopfzeile und erster
364
+ Zeile — nicht mehr oben in der Leiste, wo sie wie eine Marke zum
365
+ Geraet aussah und niemand sie mit der Toenung der Zeilen darunter
366
+ verband (Ricardo, 08.09.2026). Hier ist sie selbst ein Beispiel:
367
+ dieselbe Toenung, dieselbe Breite, unmittelbar ueber dem, was sie
368
+ erklaert. Gefuellt wird sie erst nach dem Zeichnen der Zeilen —
369
+ vorher weiss niemand, ob es etwas zu erklaeren gibt. */
370
+ var legendenPlatz = el('div');
371
+ card.appendChild(legendenPlatz);
372
+
328
373
  /* --- Zustandsliste --- */
329
374
  e.states.forEach(function (s, i) {
330
375
  var pl = platzVon[s.n];
@@ -337,7 +382,7 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
337
382
  einmal als Legende ueber der Liste, und der lange Text haengt als
338
383
  Hinweis an der Zeile. */
339
384
  var ohnePlatz = (s.on && !unfertig && !pl);
340
- if (ohnePlatz) { ohnePlatzZahl++; }
385
+ if (ohnePlatz) { ohnePlatzZahl++; ohnePlatzNamen.push(s.n); }
341
386
  var row = el('div', 'erow' + (s.on ? '' : ' skip') + (S.openRow === i ? ' open' : '') +
342
387
  (ohnePlatz ? ' ohneplatz' : ''));
343
388
  row.tabIndex = 0;
@@ -367,10 +412,16 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
367
412
  row.appendChild(mk);
368
413
 
369
414
  var n3 = el('span', 'nm3', s.n || tr('list.newPoint'));
370
- /* Die Beschriftung (kuenftiger Anzeigename) steht gedaempft hinter
371
- der Kennung - wie die Namen im Baum. Vorher tippte man sie im
372
- Detail und sah nirgends eine Wirkung. */
373
- if (s.caption && s.caption !== s.n) { n3.appendChild(el('span', 'nam', s.caption)); }
415
+ /* Die Beschriftung (kuenftiger Anzeigename) steht im Hinweis an der
416
+ Kennung, nicht in der Zeile.
417
+
418
+ Sie stand einmal gedaempft dahinter - wie die Namen im Baum, und
419
+ aus demselben Grund: wer sie im Detail tippt, soll eine Wirkung
420
+ sehen. Seit die Zeile auch Marken traegt („aus mqtt-client.0",
421
+ „geaendert", „weicht ab") wurde sie damit zu voll, und Rolle und
422
+ Wert rutschten nach rechts weg (Ricardo, 08.09.2026). Die Kennung
423
+ traegt jetzt den Hinweis; sichtbar bleibt sie im Detail. */
424
+ if (s.caption && s.caption !== s.n) { n3.title = s.caption; }
374
425
  if (s.manuell) { n3.appendChild(el('span', 'hand', tr('list.byHand'))); }
375
426
  if (s.geaendert) { n3.appendChild(el('span', 'geae', tr('list.changed'))); }
376
427
  /* Was die Vorlage anders will, steht an der Zeile - sonst sieht man
@@ -421,6 +472,41 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
421
472
  /* Die Marke ist zugleich der Griff: solange sie steht, ist die Zeile
422
473
  unbestaetigt und faellt beim Zuruecknehmen. Ein Haken laesst sie
423
474
  verschwinden, ein Kreuz nimmt genau diese eine zurueck. */
475
+ /* Der Punkt liest woanders her als der Rest.
476
+
477
+ Bewusst eine Marke und keine Flaechentoenung: die Flaeche ist
478
+ schon vergeben („kein Platz im Muster"), und beides gilt
479
+ unabhaengig voneinander - eine Zeile kann getoent und fremd sein.
480
+ Die Marke traegt nur den Adapter, nicht die ganze Kennung; die
481
+ volle steht im Hinweis. Und sie ist ruhig gehalten: sie meldet
482
+ keinen Fehler, sie sagt, woher der Wert kommt. */
483
+ var vonHier = !!hauptQuelle && s.srcR &&
484
+ (s.srcR === hauptQuelle || s.srcR.indexOf(hauptQuelle + '.') === 0);
485
+ if (s.on && s.srcR && (alleTaggen || (hauptQuelle && !vonHier))) {
486
+ /* Der Tag nennt die QUELLE, nicht den Adapter.
487
+
488
+ Er trug erst nur `mqtt-client.0` - was reichte, solange nur die
489
+ Ausreisser markiert waren. Seit am Alias jede Zeile einen Tag
490
+ traegt, muss er unterscheiden: `alias.0.Solar.Balkon.Einspeisung`
491
+ liest aus zwei mqtt-Quellen, und beide hiessen „aus
492
+ mqtt-client.0" (08.09.2026).
493
+
494
+ Gezeigt werden die letzten zwei Glieder des Quellknotens -
495
+ `Solar_Balkon`, `HM600.ch0`, `Balkon.Einspeisung`. Kurz genug
496
+ fuer die Zeile, lang genug zum Unterscheiden; die volle Kennung
497
+ steht im Hinweis. */
498
+ var qHier = aliasFuerMarke ? (quelleHier(aliasFuerMarke, s.srcR) || '') : '';
499
+ var basis = qHier || s.srcR.slice(0, s.srcR.lastIndexOf('.'));
500
+ var teileQ = basis.split('.');
501
+ var kurzQ = teileQ.slice(Math.max(2, teileQ.length - 2)).join('.') || basis;
502
+ var fq = el('span', 'geae fremdquelle', tr('list.otherSource', kurzQ));
503
+ /* Am Alias ist es eine blosse Herkunftsangabe, an einer Quelle
504
+ ein Unterschied - der Hinweis sagt beides jeweils richtig. */
505
+ fq.title = alleTaggen
506
+ ? tr('list.sourceHint', s.srcR)
507
+ : tr('list.otherSourceHint', hauptQuelle, s.srcR);
508
+ n3.appendChild(fq);
509
+ }
424
510
  if (s.geraten) {
425
511
  n3.appendChild(el('span', 'rat', tr('guess.mark')));
426
512
  var jaK = el('span', 'ratknopf', '\u2713');
@@ -554,16 +640,57 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
554
640
  });
555
641
  card.appendChild(add);
556
642
 
557
- /* Die Legende zum Streifen — einmal, und nur wenn es etwas zu erklaeren
558
- gibt. Sie steht in der Leiste ueber der Liste, gleich neben der Zahl:
559
- dort sucht man die Erklaerung fuer „11 von 42", nicht am Fuss. */
643
+ /* Die Legende — einmal, und nur wenn es etwas zu erklaeren gibt.
644
+
645
+ Zwei Anlaeufe hat sie gebraucht. Zuerst stand sie in der Leiste mit
646
+ einem 14 px grossen Farbkaestchen daneben: die Toenung ist
647
+ absichtlich blass, das Kaestchen war winzig, der Text in --ink-3 —
648
+ zusammen sah man nichts. Dann als hinterlegte Marke in der Leiste:
649
+ sichtbar, aber sie las sich wie eine Aussage ueber das GERAET, und
650
+ dass die getoenten Zeilen darunter gemeint sind, sagte sie nicht
651
+ (Ricardo, 08.09.2026, beide Male).
652
+
653
+ Jetzt steht sie in der Liste selbst, direkt ueber der ersten Zeile,
654
+ in derselben Toenung und ueber dieselbe Breite. Sie erklaert die
655
+ Toenung nicht, sie zeigt sie.
656
+
657
+ Die lange Erklaerung steht hinter einem „i" daneben — sie ersetzt
658
+ die Karte, die frueher unten stand („Gehoert nicht zum <Muster>").
659
+ Zweimal dieselbe Auskunft, einmal oben und einmal weit unten, war
660
+ eine zu viel.
661
+
662
+ Als Tooltip taugte sie nicht: der Kasten wurde ueber zehn Zeilen
663
+ hoch und legte sich ueber die Liste, die man gerade lesen wollte
664
+ (Ricardo, 08.09.2026). Ein Klick auf das „i" oeffnet sie stattdessen
665
+ als Dialog — dort darf sie so lang sein, wie sie ist. */
560
666
  if (ohnePlatzZahl) {
561
- var lg = el('span', 'legende');
562
- lg.appendChild(el('span', 'lgmarke'));
563
- lg.appendChild(document.createTextNode(
564
- tr('pattern.noPlaceIn', musterName(e.want) || e.want || '?')));
565
- lg.title = tr('pattern.noPlaceLong', musterName(e.want) || e.want || '?');
566
- bar.insertBefore(lg, bar.querySelector('.sp2'));
667
+ var mn = musterName(e.want) || e.want || '?';
668
+ var lg = el('div', 'legendenzeile');
669
+ lg.appendChild(document.createTextNode(tr('pattern.noPlaceLegend', mn)));
670
+
671
+ var ib = el('button', 'infoknopf', 'i');
672
+ ib.type = 'button';
673
+ ib.title = tr('info.more');
674
+ ib.setAttribute('aria-label', tr('info.more'));
675
+ ib.addEventListener('click', function (ev) {
676
+ ev.stopPropagation();
677
+ var t = $('#info-titel');
678
+ /* „Gehoert nicht zum Steckdose" waere falsches Deutsch — mit
679
+ „-Muster" dahinter stimmt es fuer jeden Musternamen. */
680
+ if (t) { t.textContent = tr('info.dlgTitle', mn); }
681
+ var b = $('#info-body');
682
+ if (b) {
683
+ b.textContent = '';
684
+ var k = el('div', 'aside w');
685
+ k.appendChild(el('b', null, ohnePlatzNamen.join(', ')));
686
+ k.appendChild(document.createTextNode(' — ' + tr('info.explain', mn)));
687
+ b.appendChild(k);
688
+ }
689
+ var d = $('#dlg-info');
690
+ if (d && !d.open) { d.showModal(); }
691
+ });
692
+ lg.appendChild(ib);
693
+ legendenPlatz.appendChild(lg);
567
694
  }
568
695
 
569
696
  host.appendChild(card);
package/admin/tab.html CHANGED
@@ -120,6 +120,14 @@
120
120
  </div>
121
121
  </dialog>
122
122
 
123
+ <dialog id="dlg-info" style="max-width:600px;">
124
+ <h3 id="info-titel">Geh&ouml;rt nicht zum Muster</h3>
125
+ <div class="dbody" id="info-body"></div>
126
+ <div class="dfoot">
127
+ <button class="btn primary" data-close>Schlie&szlig;en</button>
128
+ </div>
129
+ </dialog>
130
+
123
131
  <dialog id="dlg-guess" style="max-width:560px;">
124
132
  <h3 id="guess-titel">Freie Pl&auml;tze trotz Vorlage?</h3>
125
133
  <div class="dbody" id="guess-body"></div>
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "alias-workbench",
4
- "version": "0.8.2",
4
+ "version": "0.9.0",
5
5
  "news": {
6
+ "0.9.0": {
7
+ "en": "Several sources behind one alias are now visible: a chip names their count, every row its origin, and the way back leads from each source to the alias. The fields “reads from” and “writes to” show the full id, the pattern names now come from the admin, and values keep running in the alias view. A complete run on the production system brought four findings: the role list was half empty because the detector delivers its slot expressions as text with slashes — 210 roles instead of 232, and BRIGHTNESS without a role in six patterns; and a point the template lists under a different name now keeps its role and its caption at the source.",
8
+ "de": "Mehrere Quellen an einem Alias werden sichtbar: ein Chip nennt ihre Zahl, jede Zeile ihre Herkunft, und der Rückweg führt von jeder Quelle zum Alias. Die Felder „liest aus“ und „schreibt auf“ zeigen die volle Kennung, die Musternamen kommen jetzt aus dem Admin, und am Alias laufen die Werte mit. Der komplette Durchlauf am Produktivsystem brachte vier Befunde: Die Rollenliste war halb leer, weil der Detektor seine Platzausdrücke als Text mit Schrägstrichen liefert — 210 statt 232 Rollen, und BRIGHTNESS ohne Rolle in sechs Mustern; und ein Punkt, den die Vorlage unter anderem Namen führt, behält an der Quelle jetzt seine Rolle und seine Beschriftung.",
9
+ "ru": "Несколько источников одного алиаса теперь видны: чип называет их число, каждая строка — своё происхождение. Поля чтения и записи показывают полный идентификатор, имена шаблонов берутся из админки, а значения обновляются в режиме алиаса. Полный прогон на рабочей системе выявил четыре находки: список ролей был наполовину пуст, потому что детектор выдаёт выражения как текст со слешами — 210 ролей вместо 232; и точка, которую шаблон называет иначе, теперь сохраняет у источника свою роль и подпись.",
10
+ "pt": "Várias fontes de um alias agora são visíveis: um chip indica o seu número e cada linha a sua origem. Os campos de leitura e escrita mostram o identificador completo, os nomes dos padrões vêm agora do admin e os valores acompanham na vista do alias. Uma passagem completa no sistema produtivo trouxe quatro constatações: a lista de funções estava meio vazia porque o detetor entrega as suas expressões como texto com barras — 210 em vez de 232; e um ponto que o modelo designa de outra forma mantém agora a sua função e a sua legenda na fonte.",
11
+ "nl": "Meerdere bronnen achter één alias zijn nu zichtbaar: een chip noemt hun aantal en elke regel zijn herkomst. De velden voor lezen en schrijven tonen de volledige id, de patroonnamen komen nu uit de admin en de waarden lopen mee in de aliasweergave. Een volledige doorloop op het productiesysteem leverde vier bevindingen op: de rollenlijst was half leeg omdat de detector zijn uitdrukkingen als tekst met slashes levert — 210 in plaats van 232; en een punt dat de sjabloon anders noemt behoudt nu zijn rol en zijn bijschrift bij de bron.",
12
+ "fr": "Plusieurs sources derrière un alias sont désormais visibles : une pastille indique leur nombre et chaque ligne son origine. Les champs de lecture et d'écriture affichent l'identifiant complet, les noms de modèles viennent maintenant de l'admin et les valeurs suivent dans la vue alias. Un parcours complet sur le système de production a donné quatre constats : la liste des rôles était à moitié vide car le détecteur livre ses expressions sous forme de texte avec des barres obliques — 210 au lieu de 232 ; et un point que le modèle nomme autrement conserve désormais son rôle et son libellé à la source.",
13
+ "it": "Più sorgenti dietro un alias sono ora visibili: un chip ne indica il numero e ogni riga la propria origine. I campi di lettura e scrittura mostrano l'identificatore completo, i nomi dei modelli provengono ora dall'admin e i valori si aggiornano nella vista alias. Un giro completo sul sistema di produzione ha prodotto quattro riscontri: l'elenco dei ruoli era mezzo vuoto perché il rilevatore consegna le sue espressioni come testo con barre — 210 invece di 232; e un punto che il modello chiama in altro modo mantiene ora il suo ruolo e la sua didascalia alla sorgente.",
14
+ "es": "Varias fuentes detrás de un alias son ahora visibles: un chip indica su número y cada fila su origen. Los campos de lectura y escritura muestran el identificador completo, los nombres de patrones vienen ahora del admin y los valores se actualizan en la vista de alias. Una pasada completa en el sistema productivo dio cuatro hallazgos: la lista de roles estaba medio vacía porque el detector entrega sus expresiones como texto con barras — 210 en lugar de 232; y un punto que la plantilla nombra de otra forma conserva ahora su rol y su etiqueta en la fuente.",
15
+ "pl": "Kilka źródeł jednego aliasu jest teraz widocznych: chip podaje ich liczbę, a każdy wiersz swoje pochodzenie. Pola odczytu i zapisu pokazują pełny identyfikator, nazwy wzorców pochodzą teraz z admina, a wartości aktualizują się w widoku aliasu. Pełny przebieg na systemie produkcyjnym przyniósł cztery ustalenia: lista ról była w połowie pusta, ponieważ detektor dostarcza swoje wyrażenia jako tekst z ukośnikami — 210 zamiast 232; a punkt, który szablon nazywa inaczej, zachowuje teraz swoją rolę i swój podpis przy źródle.",
16
+ "uk": "Кілька джерел одного аліаса тепер видно: чіп називає їх кількість, кожен рядок — своє походження. Поля читання та запису показують повний ідентифікатор, імена шаблонів надходять з адмінки, а значення оновлюються у вигляді аліаса. Повний прогін на робочій системі дав чотири висновки: список ролей був напівпорожній, бо детектор видає свої вирази як текст з косими рисками — 210 замість 232; і точка, яку шаблон називає інакше, тепер зберігає біля джерела свою роль і свій підпис.",
17
+ "zh-cn": "现在可以看到一个别名背后的多个来源:标签显示数量,每一行显示其来源。读取和写入字段显示完整标识符,模式名称现在来自管理界面,别名视图中的数值会持续更新。在生产系统上的一次完整测试带来了四项发现:角色列表只有一半,因为检测器以带斜杠的文本形式提供其表达式——210 个而不是 232 个;模板以其他名称列出的数据点,现在在来源处保留其角色和标签。"
18
+ },
19
+ "0.8.3": {
20
+ "en": "What is stored in an existing alias now holds: its caption stays, a read-only alias stays read-only, and that applies to every point, including several pointing at one and the same source. An alias whose channel exists only as an id is compared at all now, and the interface no longer announces a creation where it changes. The dry run shows enumerations as a comparison, and a line reading from another adapter gets its value on the first click.",
21
+ "de": "Was in einem vorhandenen Alias steht, gilt jetzt: die Beschriftung bleibt, ein Nur-Lese-Alias bleibt lesend, und das für jeden Punkt — auch wenn mehrere auf dieselbe Quelle zeigen. Ein Alias, dessen Kanal nur als Kennung besteht, wird überhaupt verglichen, und die Oberfläche kündigt kein Anlegen mehr an, wo sie ändert. Der Trockenlauf zeigt Aufzählungen im Vergleich, und eine Zeile aus einem anderen Adapter bekommt ihren Wert schon beim ersten Klick.",
22
+ "ru": "То, что записано в существующем алиасе, теперь имеет приоритет: подпись сохраняется, алиас только для чтения остаётся таким же, и это касается каждой точки — даже если несколько указывают на один источник. Алиас, канал которого существует лишь как идентификатор, теперь вообще сравнивается, а интерфейс больше не объявляет создание там, где он изменяет. Пробный прогон показывает перечисления в сравнении, а строка из другого адаптера получает значение уже при первом щелчке.",
23
+ "pt": "O que está guardado num alias existente passa a valer: a legenda mantém-se, um alias só de leitura continua só de leitura, e isso para cada ponto — mesmo quando vários apontam para a mesma fonte. Um alias cujo canal existe apenas como identificador passa a ser comparado, e a interface deixa de anunciar uma criação onde altera. A simulação mostra as enumerações em comparação, e uma linha de outro adaptador recebe o seu valor logo no primeiro clique.",
24
+ "nl": "Wat in een bestaande alias staat, geldt nu: het opschrift blijft, een alleen-lezen alias blijft alleen-lezen, en dat voor elk punt — ook als meerdere naar dezelfde bron wijzen. Een alias waarvan het kanaal alleen als id bestaat, wordt nu überhaupt vergeleken, en de interface kondigt geen aanmaak meer aan waar zij wijzigt. De proefronde toont opsommingen in vergelijking, en een regel uit een andere adapter krijgt zijn waarde al bij de eerste klik.",
25
+ "fr": "Ce qui figure dans un alias existant fait désormais foi : la légende reste, un alias en lecture seule le demeure, et cela pour chaque point — même lorsque plusieurs pointent vers la même source. Un alias dont le canal n’existe que comme identifiant est enfin comparé, et l’interface n’annonce plus une création là où elle modifie. La simulation montre les énumérations en comparaison, et une ligne provenant d’un autre adaptateur obtient sa valeur dès le premier clic.",
26
+ "it": "Quello che è memorizzato in un alias esistente ora vale: la didascalia resta, un alias di sola lettura rimane tale, e questo per ogni punto — anche quando più punti indicano la stessa sorgente. Un alias il cui canale esiste solo come identificatore viene finalmente confrontato, e l’interfaccia non annuncia più una creazione dove modifica. La prova a vuoto mostra le enumerazioni a confronto, e una riga che legge da un altro adattatore ottiene il suo valore già al primo clic.",
27
+ "es": "Lo que está guardado en un alias existente ahora manda: el rótulo se mantiene, un alias de solo lectura sigue siéndolo, y eso para cada punto — incluso cuando varios apuntan a la misma fuente. Un alias cuyo canal existe solo como identificador por fin se compara, y la interfaz ya no anuncia una creación donde modifica. La simulación muestra las enumeraciones en comparación, y una línea que lee de otro adaptador obtiene su valor ya en el primer clic.",
28
+ "pl": "To, co zapisano w istniejącym aliasie, teraz obowiązuje: podpis zostaje, alias tylko do odczytu pozostaje taki, i dotyczy to każdego punktu — także gdy kilka wskazuje to samo źródło. Alias, którego kanał istnieje wyłącznie jako identyfikator, jest wreszcie porównywany, a interfejs nie zapowiada już tworzenia tam, gdzie zmienia. Przebieg próbny pokazuje wyliczenia w porównaniu, a wiersz czytający z innego adaptera dostaje wartość już przy pierwszym kliknięciu.",
29
+ "uk": "Те, що записано в наявному аліасі, тепер має силу: підпис залишається, аліас лише для читання таким і лишається, і це для кожної точки — навіть коли кілька вказують на те саме джерело. Аліас, канал якого існує лише як ідентифікатор, нарешті порівнюється, а інтерфейс більше не оголошує створення там, де змінює. Пробний прогін показує переліки в порівнянні, а рядок з іншого адаптера отримує значення вже при першому кліку.",
30
+ "zh-cn": "现有别名中已保存的内容现在具有优先权:标题保留,只读别名仍为只读,并且对每个数据点都适用——即使多个点指向同一数据源。通道仅以标识符形式存在的别名现在也会被比对,界面不再在修改时宣告“新建”。试运行会以对比方式显示枚举,来自其他适配器的数据行在第一次点击时就能取到值。"
31
+ },
6
32
  "0.8.2": {
7
33
  "en": "A second alias on the same source is now announced instead of created silently — with a button to move the existing one instead. Switching to a template that finds nothing now works. Roles the workbench adapted are marked and can be taken back. Escape discards in every field of the target bar. New adapter icon.",
8
34
  "de": "Ein zweiter Alias auf dieselbe Quelle wird jetzt angesagt statt stillschweigend angelegt — daneben ein Knopf, der stattdessen den vorhandenen verlegt. Der Wechsel auf eine Vorlage, die nichts findet, greift jetzt. Angepasste Rollen sind markiert und lassen sich zurückholen. Escape verwirft in jedem Feld des Zielbalkens. Neues Adaptersymbol.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.alias-workbench",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Creates aliases the comfortable way — devices that vis, Alexa and the device adapter understand. Templates do the work: shipped ones for HomeMatic and Tasmota, your own for everything else. Live values while you build, dry run before anything is written.",
5
5
  "author": {
6
6
  "name": "Ricardo Hipp",