iobroker.alias-workbench 0.9.9 → 0.9.10

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.
@@ -445,12 +445,31 @@ export function zuSchreiben(alleAusgaenge) {
445
445
  }).filter(Boolean);
446
446
  }
447
447
  if (!alleAusgaenge || !S.entwurf.instanzen || S.entwurf.instanzen.length < 2) { return [S.entwurf]; }
448
+ /* Der Kanalname wird zur Kennung - und zwei koennen dabei gleich werden.
449
+
450
+ `ausgangName` saeubert seit dem 12.09.2026 selbst (G31), und damit
451
+ ergeben „Licht Bar" und „Licht, Bar" beide `Licht_Bar`: der zweite
452
+ Kanal ueberschriebe den ersten, still und ohne Meldung. Deshalb
453
+ wird hier mitgezaehlt, was schon vergeben ist - der zweite bekommt
454
+ `_2`, der dritte `_3`. Der Anzeigename bleibt der des Kanals. */
455
+ var vergeben = {};
456
+ var eindeutig = function (name) {
457
+ if (!vergeben[name]) { vergeben[name] = 1; return name; }
458
+ var z = 1, n2;
459
+ do { z++; n2 = name + '_' + z; } while (vergeben[n2]);
460
+ vergeben[n2] = 1;
461
+ return n2;
462
+ };
448
463
  return S.entwurf.instanzen.map(function (n) {
449
- if (String(n) === String(S.entwurf.instanz)) { return S.entwurf; }
464
+ if (String(n) === String(S.entwurf.instanz)) {
465
+ S.entwurf.zielName = eindeutig(S.entwurf.zielName);
466
+ S.entwurf.ziel = S.entwurf.zielOrdner + '.' + S.entwurf.zielName;
467
+ return S.entwurf;
468
+ }
450
469
  var v = vorschlag(S.current, S.entwurf.vorlage, n);
451
470
  if (v) {
452
471
  v.zielOrdner = S.entwurf.zielOrdner;
453
- v.zielName = ausgangName(v, n);
472
+ v.zielName = eindeutig(ausgangName(v, n));
454
473
  v.ziel = v.zielOrdner + '.' + v.zielName;
455
474
  enumsUebertragen(v);
456
475
  }
@@ -487,6 +506,35 @@ function sammle(liste, o) {
487
506
  vorh.neu = vorh.neu && o.neu;
488
507
  }
489
508
 
509
+ /* Eine selbst angelegte Aufzaehlung, die durch diesen Schreibvorgang
510
+ leer wird, geht mit.
511
+
512
+ Beim LOESCHEN eines Alias tut das `enumsDieLeerWuerden` seit jeher.
513
+ Beim blossen WECHSEL der Funktion nicht: wer „Stehlampe" waehlte,
514
+ schrieb, dann auf „Licht" umstellte, hinterliess ein leeres
515
+ `enum.functions.floor_lamp` - von der Werkbank angelegt, von der
516
+ Werkbank geleert, und trotzdem stehengeblieben (gemessen 12.09.2026).
517
+
518
+ Dieselbe Regel wie dort: nur was `angelegtVon` traegt, und nur wenn
519
+ hinterher kein Mitglied uebrig ist. Der Eintrag wandert aus der
520
+ Schreibliste in die Loeschliste - sonst schriebe ihn `setObject`
521
+ parallel zum `delObject` wieder hin.
522
+
523
+ Gerufen von `zeigeTrockenlauf` UND `schreibeObjekte`: beide bauen ihre
524
+ Liste selbst, und was der Trockenlauf zeigt, muss das Schreiben tun. */
525
+ function leereEigeneEnums(liste, weg) {
526
+ liste.slice().forEach(function (x) {
527
+ if (!x.istEnum || !x.weg) { return; }
528
+ var o = enums[x.id];
529
+ if (!o || !o.native || o.native.angelegtVon !== 'alias-workbench') { return; }
530
+ if (((x.obj.common || {}).members || []).length) { return; }
531
+ liste.splice(liste.indexOf(x), 1);
532
+ if (!weg.some(function (y) { return y.id === x.id; })) {
533
+ weg.push({ id: x.id, vorgabe: true, grund: tr('enums.nowEmpty') });
534
+ }
535
+ });
536
+ }
537
+
490
538
  export function zeigeTrockenlauf(alleAusgaenge) {
491
539
  if (!S.entwurf) { return; }
492
540
  S.trockenAlle = !!alleAusgaenge;
@@ -532,6 +580,7 @@ export function zeigeTrockenlauf(alleAusgaenge) {
532
580
  if (!weg.some(function (x) { return x.id === v.id; })) { weg.push(v); }
533
581
  });
534
582
  });
583
+ leereEigeneEnums(liste, weg);
535
584
  S.loeschListe = weg;
536
585
  var body = $('#dry-body');
537
586
  body.textContent = '';
@@ -600,6 +649,20 @@ export function zeigeTrockenlauf(alleAusgaenge) {
600
649
  body.appendChild(zwK);
601
650
  }
602
651
 
652
+ /* Was beim letzten Anlegen der Sendepunkte schiefging - hier oben, wo
653
+ man es sieht, und nicht als Tooltip an einem Knopf, den derselbe
654
+ Rueckruf gleich wieder wegwirft (H17). */
655
+ if (S.mqFehler && S.mqFehler.length) {
656
+ var mk = el('div', 'aside w');
657
+ mk.style.borderLeftColor = 'var(--bad)';
658
+ mk.style.marginBottom = '11px';
659
+ mk.appendChild(el('b', null, tr('write.failed', S.mqFehler.length)));
660
+ var mul = el('ul');
661
+ S.mqFehler.forEach(function (x) { mul.appendChild(el('li', null, x)); });
662
+ mk.appendChild(mul);
663
+ body.appendChild(mk);
664
+ }
665
+
603
666
  /* Die Aufzaehlungen sind gerade nicht bekannt — dann wird auch keine
604
667
  geschrieben. Das gehoert hierher und nicht in eine Sperre: der Alias
605
668
  selbst braucht sie nicht, und aus einem stillen Datenverlust eine
@@ -618,7 +681,12 @@ export function zeigeTrockenlauf(alleAusgaenge) {
618
681
  var wch = el('div', 'ch');
619
682
  wch.appendChild(el('span', 'typ', tr('write.remove')));
620
683
  wch.appendChild(el('span', 'chip bad', tr('write.removeCount', weg.length)));
621
- wch.appendChild(el('span', 'chip mut', tr('write.removeHint')));
684
+ /* Der Satz gilt fuer Datenpunkte. Steht eine leer gewordene
685
+ Aufzaehlung mit in der Liste, waere er dort falsch - die liegt
686
+ nicht im Alias und stand nie im Entwurf (12.09.2026). */
687
+ if (weg.some(function (v) { return String(v.id).indexOf('enum.') !== 0; })) {
688
+ wch.appendChild(el('span', 'chip mut', tr('write.removeHint')));
689
+ }
622
690
  wk.appendChild(wch);
623
691
  weg.forEach(function (v, i) {
624
692
  var r = el('div', 'slot');
@@ -685,6 +753,19 @@ export function zeigeTrockenlauf(alleAusgaenge) {
685
753
  kn2.disabled = false;
686
754
  kn2.textContent = tr('write.retry');
687
755
  kn2.title = mqFehler.join(' | ');
756
+ /* Und zwar so, dass es den Neuaufbau ueberlebt.
757
+
758
+ Drei Zeilen weiter unten wird der Entwurf neu gebaut
759
+ und der Trockenlauf noch einmal gerechnet - dabei
760
+ verschwindet dieser Knopf samt seiner Begruendung aus
761
+ dem DOM. Gemessen 12.09.2026: nach rund 120 ms war er
762
+ weg. Der neue Dialog sperrte wieder und sagte, welcher
763
+ Sendepunkt fehlt, aber nicht mehr, WARUM er nicht
764
+ entstanden ist (H17). Am Zustand gemerkt, zeichnet
765
+ `zeigeTrockenlauf` ihn oben wieder hin. */
766
+ S.mqFehler = mqFehler.slice();
767
+ } else {
768
+ S.mqFehler = null;
688
769
  }
689
770
  /* Erst den Entwurf neu aufbauen lassen, dann den
690
771
  Trockenlauf noch einmal rechnen. Sofort geoeffnet zeigte
@@ -1024,6 +1105,16 @@ export function loescheAlias() {
1024
1105
  m.style.fontSize = '13px';
1025
1106
  if (fehler.length) {
1026
1107
  m.appendChild(el('b', null, tr('write.failed', fehler.length)));
1108
+ /* Und WAS schiefging, nicht nur wie viel.
1109
+
1110
+ Beim Schreiben steht die Liste seit jeher da (`melden` beim
1111
+ Verlegen, der Trockenlauf-Dialog beim Anlegen); beim Loeschen
1112
+ fehlte sie, und zurueck blieb die blosse Zahl. Wer wissen
1113
+ will, welcher Punkt haengt — etwa weil ein Skript ihn sperrt
1114
+ —, erfuhr es nicht (G44, gemessen 12.09.2026). */
1115
+ var ul = el('ul');
1116
+ fehler.forEach(function (x) { ul.appendChild(el('li', null, x)); });
1117
+ m.appendChild(ul);
1027
1118
  } else {
1028
1119
  m.appendChild(el('b', null, tr('del.done', alle.length)));
1029
1120
  }
@@ -1674,6 +1765,10 @@ export function schreibeObjekte() {
1674
1765
  schlecht += pruefeSchreiben(en).length;
1675
1766
  });
1676
1767
  if (schlecht) { return; }
1768
+ /* Dieselbe Bereinigung wie im Trockenlauf - diese Liste entsteht neu,
1769
+ und ohne den Griff hier schriebe `setObject` die leere Aufzaehlung
1770
+ parallel zum `delObject` wieder hin. */
1771
+ leereEigeneEnums(liste, S.loeschListe);
1677
1772
 
1678
1773
  var body = $('#dry-body');
1679
1774
  var offen = liste.length;
@@ -56,7 +56,12 @@ export function ladeSprache(fertig) {
56
56
 
57
57
  var beantwortet = false;
58
58
  socket.emit('getObject', 'system.config', function (err, obj) {
59
- if (beantwortet) { return; }
59
+ /* Hier stand `if (beantwortet) { return; }` - der Waechter aus der
60
+ Zeit vor dem Nachziehen. Er stieg genau im Fall aus, fuer den der
61
+ Zweig weiter unten gebaut wurde, und machte ihn zu totem Text
62
+ (gemessen 12.09.2026: mit `expertMode: true` am System und einer
63
+ um 4 s verzoegerten Antwort blieb `expertenVorgabe` auch nach
64
+ zehn Sekunden auf `false`). */
60
65
  /* Auch eine spaete Antwort gilt noch.
61
66
 
62
67
  Nach 2,5 Sekunden geht es ohne weiter — richtig, sonst haengt der
@@ -7,7 +7,7 @@
7
7
  import { S } from './zustand.js';
8
8
  import { D, $, el } from './basis.js';
9
9
  import { hatPunkt, jsonVon, feldWert, kindZustaende, feldFormel } from './werte.js';
10
- import { eigenerKanalName, merkeHakenVorgabe } from './entwurf.js';
10
+ import { eigenerKanalName, merkeHakenVorgabe, kennungtauglich } from './entwurf.js';
11
11
  import { tasmotaBefehle } from './mqtt.js';
12
12
  import { socket } from './verbindung.js';
13
13
 
@@ -271,7 +271,17 @@ export function setzeInstanz(text, v, n) {
271
271
  export function ausgangName(e, n) {
272
272
  if (e && e.kanal) {
273
273
  var eigen = eigenerKanalName(e.kanal + '.' + n);
274
- if (eigen) { return eigen.replace(/\./g, '_'); }
274
+ /* Der Kanalname wird hier zur KENNUNG - also durch dieselbe
275
+ Saeuberung wie ueberall sonst.
276
+
277
+ Vorher stand hier nur `replace(/\./g, '_')`. Leerzeichen und
278
+ Kommas blieben damit stehen, und der js-controller lehnt so etwas
279
+ nicht ab, er saeubert still: aus dem Kanalnamen „Licht, Bar" wurde
280
+ `alias.0.…Licht_ Bar`, waehrend die Werkbank „Licht, Bar" anzeigte
281
+ und in die Aufzaehlungen eintrug - eine Kennung, die es nie gab
282
+ (G31, gemessen 12.09.2026). Der Name selbst bleibt unberuehrt: er
283
+ steht als Beschriftung im Objekt. */
284
+ if (eigen) { return kennungtauglich(eigen) || eigen.replace(/\./g, '_'); }
275
285
  }
276
286
  if (!e || !e.kanalnameRoh) { return 'POWER' + n; }
277
287
  return String(e.kanalnameRoh).split(e.platzhalter || '%N%').join(n);
@@ -2058,19 +2058,81 @@ export function zeichneVorlagenDialog() {
2058
2058
 
2059
2059
  var b = $('#btn-tpl-save');
2060
2060
  if (b) {
2061
- b.disabled = fehlerErk || !k.id || !!fremdeMitId(k) ||
2062
- !hinweisTaugt(k.namenshinweis) ||
2063
- !z.zeilen.filter(function (r) { return !r.weg; }).length;
2061
+ /* Fuenf Gruende sperren den Knopf - und jeder von ihnen gehoert
2062
+ hingeschrieben.
2063
+
2064
+ Bisher wurde nur `disabled` gesetzt, `title` blieb leer. Bei
2065
+ fehlenden Pflichtpunkten stand die Erklaerung weiter unten in der
2066
+ Probelauf-Karte, fuer die man scrollen musste; bei den anderen
2067
+ vier stand sie nirgends. Ricardo stiess darauf, als er fuer den
2068
+ Objektdump eine eigene Vorlage anlegen wollte - grauer Knopf,
2069
+ kein Wort dazu (I32, 11.09.2026). Ein Knopf, der nicht sagt warum,
2070
+ ist derselbe Fehler wie eine Sperre ohne Begruendung (U28). */
2071
+ var gruende = sperrgruende(z);
2072
+ b.disabled = gruende.length > 0;
2073
+ b.title = gruende.join(' · ');
2064
2074
  b.textContent = k.modus === 'update' ? tr('tpls.saveUpdate') : tr('tpls.saveNew');
2075
+ if (gruende.length) {
2076
+ var sk = el('div', 'aside w');
2077
+ sk.style.borderLeftColor = 'var(--bad)';
2078
+ sk.appendChild(el('b', null, tr('tpls.lockedWhy')));
2079
+ var sul = el('ul');
2080
+ gruende.forEach(function (x) { sul.appendChild(el('li', null, x)); });
2081
+ sk.appendChild(sul);
2082
+ body.appendChild(sk);
2083
+ }
2065
2084
  }
2066
2085
  var tt = $('#tpl-titel');
2067
2086
  if (tt) { tt.textContent = tr('tpls.title', z.e.kanal.split('.').pop()); }
2068
2087
  }
2069
2088
 
2089
+ /* Die fuenf Gruende, die das Speichern sperren - an einer Stelle.
2090
+
2091
+ Gebraucht werden sie zweimal: beim Zeichnen, um Knopf und Kasten zu
2092
+ beschriften, und beim Speichern selbst. Das zweite ist keine
2093
+ Doppelung, sondern die eigentliche Sicherung: Der Knopf wird beim
2094
+ VERLASSEN eines Feldes neu bewertet, nicht beim Tippen. Wer eine
2095
+ offene Klammer in den Namenshinweis schreibt und ohne Umweg auf
2096
+ „Vorlage speichern" klickt, hat einen blauen Knopf vor sich - und die
2097
+ Vorlage wurde mit `namenshinweis: "("` gespeichert (Ricardo,
2098
+ 12.09.2026, am Testsystem nachgestellt; `hinweisTrifft` faengt den
2099
+ kaputten Ausdruck zwar ab, aber gespeichert gehoert er trotzdem
2100
+ nicht). */
2101
+ export function sperrgruende(z) {
2102
+ if (!z) { return []; }
2103
+ var k = z.kopf;
2104
+ var neu = baueVorlage(z);
2105
+ var gruende = [];
2106
+ if (!neu.erkennung.erforderlich.length) { gruende.push(tr('tpls.lockNoRequired')); }
2107
+ if (!k.id) { gruende.push(tr('tpls.lockNoId')); }
2108
+ var fremd = fremdeMitId(k);
2109
+ if (fremd) { gruende.push(tr('tpls.lockIdTaken', fremd.id)); }
2110
+ if (!hinweisTaugt(k.namenshinweis)) { gruende.push(tr('tpls.lockBadHint')); }
2111
+ if (!z.zeilen.filter(function (r) { return !r.weg; }).length) { gruende.push(tr('tpls.lockNoRows')); }
2112
+ return gruende;
2113
+ }
2114
+
2070
2115
  export function speichereVorlageAusDialog() {
2071
2116
  if (!S.tplZ) { return; }
2072
2117
  var neu = baueVorlage(S.tplZ);
2073
2118
 
2119
+ /* Letzte Instanz, unabhaengig vom Zustand des Knopfes. */
2120
+ var gesperrt = sperrgruende(S.tplZ);
2121
+ if (gesperrt.length) {
2122
+ var bodyS = $('#tpl-body');
2123
+ var altS = bodyS.querySelector('.sperrgrund-kasten');
2124
+ if (altS) { altS.remove(); }
2125
+ var wS = el('div', 'aside w sperrgrund-kasten');
2126
+ wS.style.borderLeftColor = 'var(--bad)';
2127
+ wS.appendChild(el('b', null, tr('tpls.lockedWhy')));
2128
+ var ulS = el('ul');
2129
+ gesperrt.forEach(function (x) { ulS.appendChild(el('li', null, x)); });
2130
+ wS.appendChild(ulS);
2131
+ bodyS.insertBefore(wS, bodyS.firstChild);
2132
+ bodyS.scrollTop = 0;
2133
+ return;
2134
+ }
2135
+
2074
2136
  /* Widerspricht sie sich, wird nicht gespeichert — eine Vorlage, die
2075
2137
  auf nichts passen kann, hilft niemandem und ist spaeter schwer zu
2076
2138
  durchschauen. */
package/admin/js/werte.js CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { S } from './zustand.js';
8
8
  import { socket } from './verbindung.js';
9
- import { tr } from './sprache.js';
9
+ import { tr, txt } from './sprache.js';
10
10
  import './entwurf.js';
11
11
 
12
12
  /* Direkt nachschlagen — die Vorlagen nennen den Pfad ja vollstaendig
@@ -76,10 +76,23 @@ export function mitVorspann(pre) {
76
76
  return raus;
77
77
  }
78
78
 
79
+ /* Wie der Baum: POWER2 vor POWER10.
80
+
81
+ `keysSorted` MUSS byteweise sortiert bleiben - die Binaersuche in
82
+ `mitVorspann` haengt daran. Fuer die Anzeige ist das die falsche
83
+ Ordnung: an einer Mehrfachsteckdose stand `POWER10` vor `POWER2`,
84
+ waehrend der Baum daneben richtig zaehlte (er nimmt `Intl.Collator`
85
+ mit `numeric: true`). Also wird hier nachsortiert, wo die Liste
86
+ entsteht - je Kanal ein paar Dutzend Eintraege, nicht der
87
+ Gesamtindex (gemessen 12.09.2026). */
88
+ var ZEILEN_VERGLEICH = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
89
+
79
90
  export function kindZustaende(kanal) {
80
91
  var pre = kanal + '.';
81
92
  return mitVorspann(pre).filter(function (k) {
82
93
  return S.objects[k] && S.objects[k].type === 'state';
94
+ }).sort(function (a, b) {
95
+ return ZEILEN_VERGLEICH.compare(a.slice(pre.length), b.slice(pre.length));
83
96
  });
84
97
  }
85
98
 
@@ -112,6 +125,55 @@ export function schreibQuelle(o) {
112
125
  return (c.write === true || typeof a.write === 'string') ? (q.write || '') : '';
113
126
  }
114
127
 
128
+ /* Aus einem Alias-Objekt die Felder einer Entwurfszeile.
129
+
130
+ Dieselbe Umrechnung stand fuenfmal ausprogrammiert - dreimal in
131
+ `entwurf.js`, zweimal in `zuordnung.js` -, jedes Mal mit derselben
132
+ Schreibquellenregel und leicht anderem Drumherum. Die Kommentare an
133
+ jenen Stellen dokumentieren vier Fehler, die genau daraus entstanden
134
+ sind: eine verlorene Schreibquelle am Rollladen, ein schaltender Punkt
135
+ der zum lesenden wurde, eine fehlende Beschriftung an Solar.Netz und
136
+ eine Abweichung, die die Werkbank selbst erzeugt hatte (Y5).
137
+
138
+ Was hier steht, ist die ABLEITUNG - nicht, was damit geschieht. Die
139
+ eine Stelle vergleicht nur, die zweite legt eine Zeile an, die beiden
140
+ anderen ueberschreiben eine bestehende; das bleibt bei den Aufrufern.
141
+
142
+ `baueEntwurf` ruft sie bewusst NICHT: die Funktion baut Zeilen fuer
143
+ zwei Faelle, Alias und rohe Quelle, und drei der Felder haengen dort
144
+ an dieser Unterscheidung. Sie mit Schaltern hereinzuholen waere
145
+ schlechter als zwei ehrliche Stellen.
146
+
147
+ Die Beschriftung folgt der Regel „leer, wenn sie dem Zeilennamen
148
+ gleicht" - so halten es `vomAliasUebernehmen` und der Zweig, der neue
149
+ Zeilen anlegt. `bestandVorrang` will sie anders (uebernehmen, sobald
150
+ `common.name` gesetzt ist) und behaelt dafuer eine eigene Zeile.
151
+
152
+ Und `role`/`typ` bleiben bei den beiden Stellen, die eine BESTEHENDE
153
+ Zeile ueberschreiben (`bestandVorrang`, `vomAliasUebernehmen`), an
154
+ ihrer `!== undefined`-Frage: dort steht schon der Vorlagenwert, und
155
+ ein Alias, der gar keine Rolle nennt, soll ihn behalten statt ihn
156
+ gegen den Leerstring zu tauschen, den diese Funktion liefert. Wer das
157
+ wegraeumt, nimmt jedem vorlagenbasierten Alias seine Rollen. */
158
+ export function zeileAusAlias(o, zeilenname) {
159
+ var c = (o && o.common) || {};
160
+ var a = c.alias || {};
161
+ var q = aliasQuellen(o);
162
+ var nm = txt(c.name);
163
+ return {
164
+ role: c.role || '',
165
+ typ: c.type || '',
166
+ unit: c.unit || '',
167
+ states: c.states || undefined,
168
+ wr: !!c.write,
169
+ srcR: q.read || '',
170
+ srcW: schreibQuelle(o),
171
+ f: (typeof a.read === 'string') ? a.read : '',
172
+ fw: (typeof a.write === 'string') ? a.write : '',
173
+ caption: (nm && nm !== zeilenname) ? nm : ''
174
+ };
175
+ }
176
+
115
177
  export function aliasQuellen(obj) {
116
178
  var a = obj && obj.common && obj.common.alias;
117
179
  if (!a) { return {}; }
@@ -14,7 +14,8 @@ 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, holeEinzelne, schreibQuelle } from './werte.js';
17
+ import { kindZustaende, aliasQuellen, holeEinzelne,
18
+ zeileAusAlias } from './werte.js';
18
19
  import { aliasFuer, zielId, knotenDa } from './entwurf.js';
19
20
  import { zeichneErgebnis, entwurfAngefasst, angebotFrisch } from './ergebnis.js';
20
21
 
@@ -580,7 +581,8 @@ export function uebernehmeBestand(e) {
580
581
  hiesse, dem Vorlagenknopf den Wert zu nehmen, den er zurueckholen
581
582
  soll. */
582
583
  function vomAliasUebernehmen(z, o) {
583
- var c = o.common || {}, a = c.alias || {}, q2 = aliasQuellen(o);
584
+ var c = o.common || {}, q2 = aliasQuellen(o);
585
+ var ist = zeileAusAlias(o, z.n);
584
586
  if (!z.vorlagenWert) {
585
587
  z.vorlagenWert = {
586
588
  on: z.on, role: z.role, typ: z.typ, unit: z.unit, caption: z.caption,
@@ -593,10 +595,10 @@ export function uebernehmeBestand(e) {
593
595
  stellen, kann der Stand nicht wieder auseinanderlaufen. */
594
596
  if (c.role !== undefined) { z.role = c.role; }
595
597
  if (c.type !== undefined) { z.typ = c.type; }
596
- z.unit = c.unit || '';
597
- z.states = c.states || undefined;
598
- z.f = (typeof a.read === 'string') ? a.read : '';
599
- z.fw = (typeof a.write === 'string') ? a.write : '';
598
+ z.unit = ist.unit;
599
+ z.states = ist.states;
600
+ z.f = ist.f;
601
+ z.fw = ist.fw;
600
602
  /* Die Beschriftung steht im Alias und gehoert dem Nutzer.
601
603
 
602
604
  Sie fehlte hier als einzige - `bestandVorrang` holt sie laengst
@@ -607,8 +609,7 @@ export function uebernehmeBestand(e) {
607
609
  „Bezug" geworden (Ricardo, 07.09.2026, an alias.0.Solar.Netz).
608
610
  Gleicht die Beschriftung dem Zeilennamen, bleibt sie leer - so
609
611
  haelt es der Zweig darunter, der neue Zeilen anlegt, auch. */
610
- var nm2 = txt(c.name);
611
- z.caption = (nm2 && nm2 !== z.n) ? nm2 : '';
612
+ z.caption = ist.caption;
612
613
  if (q2.read) { z.srcR = q2.read; }
613
614
  /* Und dieselbe Schreibregel wie im Bestandsvorrang.
614
615
 
@@ -622,7 +623,7 @@ export function uebernehmeBestand(e) {
622
623
  Belegt ist das Schreiben durch `common.write` oder durch eine
623
624
  hinterlegte Schreibformel; bei getrennten Quellen zaehlt, was in
624
625
  `alias.id.write` steht. */
625
- z.srcW = schreibQuelle(o);
626
+ z.srcW = ist.srcW;
626
627
  if (q2.write && !q2.einfach && q2.write !== q2.read) {
627
628
  /* Die Gegenzeile eines Paars ist keine eigene mehr - ihre Quelle
628
629
  steckt jetzt als Schreibquelle in dieser Zeile. */
@@ -663,7 +664,7 @@ export function uebernehmeBestand(e) {
663
664
  kindZustaende(e.ziel).forEach(function (id) {
664
665
  var n = id.slice(e.ziel.length + 1);
665
666
  if (n.indexOf('.') !== -1 || kennt[n]) { return; }
666
- var o = S.objects[id], c = o.common || {}, a = c.alias || {}, q = aliasQuellen(o);
667
+ var o = S.objects[id], q = aliasQuellen(o);
667
668
 
668
669
  /* Derselbe Punkt unter anderem Namen? Dann ist es kein zweiter,
669
670
  sondern derselbe — und der Name, der schon im Alias steht, gilt.
@@ -699,31 +700,24 @@ export function uebernehmeBestand(e) {
699
700
  kennt[n] = vorhanden;
700
701
  return;
701
702
  }
702
- var nm = txt(c.name);
703
- e.states.push({
704
- n: n, on: true, ausBestand: true, nurImAlias: true,
705
- role: c.role || '', typ: c.type || '', unit: c.unit || '',
706
- states: c.states || undefined,
707
- wr: !!c.write,
708
- srcR: q.read || '',
709
- /* Dieselbe Schreibregel wie oben und im Bestandsvorrang.
710
-
711
- Hier stand `q.einfach ? '' : (q.write || '')` - bei schlichtem
712
- `alias.id` also immer leer, ohne `common.write` ueberhaupt
713
- anzusehen. Es trifft jeden Alias, bei dem MEHRERE Punkte auf
714
- denselben Quellpunkt zeigen: die eine Vorschlagszeile bekommt
715
- der erste von ihnen, alle weiteren landen hier. Am
716
- `alias.0.Badezimmer.Rollladen` zeigen OPEN, CLOSE, SET und pct
717
- alle auf `…Bad.level`; CLOSE lief oben durch und behielt seine
718
- Schreibquelle, OPEN, SET und pct verloren sie - ein
719
- Aktualisieren haette den Rollladen ueber den Alias unfahrbar
720
- gemacht (Ricardo, 08.09.2026). */
721
- srcW: schreibQuelle(o),
722
- f: (typeof a.read === 'string') ? a.read : '',
723
- fw: (typeof a.write === 'string') ? a.write : '',
724
- caption: (nm && nm !== n) ? nm : '',
725
- manuell: false
726
- });
703
+ /* Rolle, Typ, Einheit, Zustaende, Formeln, Quellen und Beschriftung
704
+ kommen geschlossen aus `zeileAusAlias` - dieselbe Ablesung wie in
705
+ `bestandVorrang`, `bestandsAbweichung` und `vomAliasUebernehmen`.
706
+
707
+ Dazu gehoert die Schreibregel: Hier stand `q.einfach ? '' :
708
+ (q.write || '')` - bei schlichtem `alias.id` also immer leer, ohne
709
+ `common.write` ueberhaupt anzusehen. Es trifft jeden Alias, bei dem
710
+ MEHRERE Punkte auf denselben Quellpunkt zeigen: die eine
711
+ Vorschlagszeile bekommt der erste von ihnen, alle weiteren landen
712
+ hier. Am `alias.0.Badezimmer.Rollladen` zeigen OPEN, CLOSE, SET und
713
+ pct alle auf `…Bad.level`; CLOSE lief oben durch und behielt seine
714
+ Schreibquelle, OPEN, SET und pct verloren sie - ein Aktualisieren
715
+ haette den Rollladen ueber den Alias unfahrbar gemacht (Ricardo,
716
+ 08.09.2026). Seit dem Zusammenlegen der vier Stellen (Y5) gilt die
717
+ Regel fuer alle zugleich. */
718
+ e.states.push(Object.assign(zeileAusAlias(o, n), {
719
+ n: n, on: true, ausBestand: true, nurImAlias: true, manuell: false
720
+ }));
727
721
  });
728
722
 
729
723
  /* Einmal nachzeichnen.
@@ -9,7 +9,7 @@
9
9
  import { S } from './zustand.js';
10
10
  import { el, $ } from './basis.js';
11
11
  import { tr } from './sprache.js';
12
- import { erkenneEntwurf, musterVon, rolleVonPlatz } from './erkennung.js';
12
+ import { erkenneEntwurf, musterVon, rolleVonPlatz, typKonflikte } from './erkennung.js';
13
13
  import { kindZustaende, wertVon, fmt } from './werte.js';
14
14
  import { steuerKanaele, vorlagenAbweichung, bestandsAbweichung, waehle, quelleHier, quellenVerteilung } from './entwurf.js';
15
15
  import { musterName } from './musternamen.js';
@@ -367,6 +367,11 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
367
367
  var legendenPlatz = el('div');
368
368
  card.appendChild(legendenPlatz);
369
369
 
370
+ /* Kippt das ganze Muster an einem einzelnen Typkonflikt? Einmal
371
+ gerechnet: die Zeilenschleife braucht es, die Legende darunter
372
+ ebenso, und beide sollen dieselbe Antwort geben. */
373
+ var kipptAnTyp = typKonflikte(e.states, e.want).filter(function (x) { return x.pflicht; })[0] || null;
374
+
370
375
  /* --- Zustandsliste --- */
371
376
  e.states.forEach(function (s, i) {
372
377
  var pl = platzVon[s.n];
@@ -378,12 +383,27 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
378
383
  06.09.2026). Jetzt traegt die Zeile eine Marke, der Satz steht
379
384
  einmal als Legende ueber der Liste, und der lange Text haengt als
380
385
  Hinweis an der Zeile. */
381
- var ohnePlatz = (s.on && !unfertig && !pl);
386
+ /* Kippt das Muster an einem Typkonflikt, wird nur der Verursacher
387
+ getoent - nicht die ganze Liste.
388
+
389
+ Dass danach keine Zeile einen Platz hat, stimmt zwar, taugt aber
390
+ nicht als Auskunft: Die Toenung liest sich als „mit dieser Zeile
391
+ stimmt etwas nicht", und das trifft auf sieben von acht nicht zu.
392
+ Sie zeigte die Folge und nicht die Ursache (Ricardo, 12.09.2026).
393
+ Die Legende darueber nennt den Grund; markiert wird die Zeile, an
394
+ der er haengt. */
395
+ var ohnePlatz = (s.on && !unfertig && !pl &&
396
+ (!kipptAnTyp || kipptAnTyp.n === s.n));
382
397
  if (ohnePlatz) { ohnePlatzZahl++; ohnePlatzNamen.push(s.n); }
383
398
  var row = el('div', 'erow' + (s.on ? '' : ' skip') + (S.openRow === i ? ' open' : '') +
384
399
  (ohnePlatz ? ' ohneplatz' : ''));
385
400
  row.tabIndex = 0;
386
- if (ohnePlatz) { row.title = tr('pattern.noPlaceLong', musterName(e.want) || e.want || '?'); }
401
+ if (ohnePlatz) {
402
+ row.title = kipptAnTyp
403
+ ? tr('pattern.noPlaceBecauseType', musterName(e.want) || e.want || '?',
404
+ kipptAnTyp.platz, kipptAnTyp.erwartet.join(tr('pattern.typeOr')), kipptAnTyp.typ)
405
+ : tr('pattern.noPlaceLong', musterName(e.want) || e.want || '?');
406
+ }
387
407
 
388
408
  var cbw = el('span');
389
409
  var cb = document.createElement('input');
@@ -670,7 +690,20 @@ export function baueListe(host, e, pl, rateKnopf, musterBlock) {
670
690
  if (ohnePlatzZahl) {
671
691
  var mn = musterName(e.want) || e.want || '?';
672
692
  var lg = el('div', 'legendenzeile');
673
- lg.appendChild(document.createTextNode(tr('pattern.noPlaceLegend', mn)));
693
+ /* Steht die ganze Liste ohne Platz da, weil EINE Zeile im falschen
694
+ Datentyp ist, sagt die Legende das - statt acht Zeilen pauschal zu
695
+ beschuldigen.
696
+
697
+ Der Ablauf: der Punkt faellt wegen des Typs aus dem Muster, damit
698
+ ist ein Pflichtplatz leer, damit greift das Muster nicht mehr, und
699
+ damit hat KEINE Zeile mehr einen Platz. Am Bastelzimmer_Licht
700
+ reichte ein Wechsel an SET von `boolean` auf `mixed`: acht von acht
701
+ Zeilen getoent, Musterfeld „✕ SET fehlt", und die Ursache stand
702
+ nur in der aufgeklappten SET-Zeile (Ricardo, 12.09.2026). */
703
+ lg.appendChild(document.createTextNode(kipptAnTyp
704
+ ? tr('pattern.noPlaceBecauseType', mn, kipptAnTyp.platz,
705
+ kipptAnTyp.erwartet.join(tr('pattern.typeOr')), kipptAnTyp.typ)
706
+ : tr('pattern.noPlaceLegend', mn)));
674
707
 
675
708
  var ib = el('button', 'infoknopf', 'i');
676
709
  ib.type = 'button';
@@ -130,6 +130,7 @@
130
130
  "enums.members": "{0} drin",
131
131
  "enums.newSuffix": "neu",
132
132
  "enums.noMatch": "nichts gefunden zu „{0}“",
133
+ "enums.nowEmpty": "ist danach leer und wird entfernt",
133
134
  "enums.placeholder": "leer = keine Zuordnung",
134
135
  "enums.removeFrom": "wird herausgenommen",
135
136
  "enums.room": "Raum",
@@ -288,6 +289,7 @@
288
289
  "pattern.manualInstead": "von Hand statt {0}",
289
290
  "pattern.missing": "{0} fehlt",
290
291
  "pattern.noMatch": "kein Muster passt zu „{0}“",
292
+ "pattern.noPlaceBecauseType": "Das {0}-Muster greift gerade nicht: {1} verlangt {2}, eingestellt ist {3}. Deshalb hat keine Zeile einen Platz.",
291
293
  "pattern.noPlaceLegend": "So hinterlegte Zeilen haben keinen Platz im {0}-Muster",
292
294
  "pattern.noPlaceLong": "kein Platz im {0}-Muster — der Punkt wird trotzdem angelegt und ist voll benutzbar, nur Alexa & Co. zählen ihn separat",
293
295
  "pattern.notDetected": "nicht erkannt",
@@ -326,14 +328,19 @@
326
328
  "roles.slot": "Platz {0} im {1}-Muster",
327
329
  "roles.slotFree": "frei",
328
330
  "roles.slotTaken": "belegt von {0}",
331
+ "swap.blocked": "Erst entscheiden — diese Punkte können nicht mitziehen: {0}. Entweder beim Tauschen entfernen oder den Tausch lassen; ein Punkt, der schreiben soll, braucht ein Schreibziel.",
329
332
  "swap.button": "Quelle tauschen …",
330
333
  "swap.diffRange": "Wertebereich {0} → {1}",
331
334
  "swap.diffType": "Typ {0} → {1}",
332
335
  "swap.diffUnit": "Einheit {0} → {1}",
333
336
  "swap.done": "Getauscht. Der Alias liest jetzt aus {0}.",
334
337
  "swap.from": "Liest heute aus:",
338
+ "swap.goHigher": "eine Ebene höher nehmen",
335
339
  "swap.gone": "Dieses Gerät gibt es nicht mehr — genau dafür ist dieser Weg gedacht.",
336
340
  "swap.guessHint": "Die mit „vermutet“ gekennzeichneten Zeilen sind über Namen oder Rolle geraten — die bitte einzeln prüfen.",
341
+ "swap.guessedByName": "Über den Punktnamen geraten: unter der neuen Quelle heißt ebenfalls etwas „{0}\". Das kann weit danebenliegen — bitte prüfen.",
342
+ "swap.guessedByRole": "Über die Rolle geraten: unter der neuen Quelle trägt ein Punkt dieselbe Rolle „{0}\". Bitte prüfen.",
343
+ "swap.higherLevel": "Eine Ebene höher ({0}) finden {1} von {2} Punkten ein Ziel, hier nur {3}.",
337
344
  "swap.keeps": "Der Alias behält seine Kennung, seine Namen, Rollen und Formeln, die Aufzeichnung in der Datenbank sowie Raum und Funktion. Geändert wird nur, woher jeder Punkt liest und wohin er schreibt.",
338
345
  "swap.mapping": "Zuordnung",
339
346
  "swap.mind": "Darauf achten:",
@@ -432,6 +439,12 @@
432
439
  "tpls.json": "Das wird gespeichert",
433
440
  "tpls.loadFailed": "eigene Vorlagen nicht geladen",
434
441
  "tpls.loadFailedHint": "Die Instanzkonfiguration hat nicht geantwortet oder einen Fehler gemeldet. Deine eigenen Vorlagen sind nicht verschwunden — sie fehlen nur in dieser Ansicht. Solange dieser Hinweis steht, kann ein Speichern eine vorhandene Vorlage überschreiben: erst neu laden.",
442
+ "tpls.lockBadHint": "Der Namenshinweis ist kein brauchbarer Ausdruck",
443
+ "tpls.lockIdTaken": "Die Kennung gehört schon „{0}“",
444
+ "tpls.lockNoId": "Die Kennung fehlt",
445
+ "tpls.lockNoRequired": "Kein Punkt ist als Pflicht angehakt — so passt die Vorlage auf gar nichts",
446
+ "tpls.lockNoRows": "Es ist keine Zeile übrig",
447
+ "tpls.lockedWhy": "Speichern ist gesperrt:",
435
448
  "tpls.losesTo": "verliert gegen {0}",
436
449
  "tpls.multiple": "Dieses Gerät hat mehrere Ausgänge",
437
450
  "tpls.multipleHelp": "Die {0} in den Pfaden wird durch %N% ersetzt — nur am Ende eines Abschnitts, damit ENERGY.Power heil bleibt.",