iobroker.alias-workbench 0.9.8 → 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.
@@ -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, rolleTrifft } from './erkennung.js';
12
+ import { musterVon, platzFuerRolle, typenFuerRolle } from './erkennung.js';
13
13
  import { musterZeile, musterName } from './musternamen.js';
14
14
  import { ladeVorlagen, aendereVorlagen, setzeMeta, pruefeVorlage, hinweisTaugt, INSTANZ_ID } from './vorlagen.js';
15
15
  import { kindZustaende, hatPunkt, feldAusFormel } from './werte.js';
@@ -283,9 +283,12 @@ export function zeichneVorlagenBlatt(host) {
283
283
  var st = el('select', 'tx');
284
284
  st.disabled = !eigen;
285
285
  st.appendChild(opt('', tr('tpls.noType')));
286
+ /* Wie im Speichern-Dialog: der Wert ist der Typ, nicht der
287
+ Musterschluessel. musterwahl.js loest es seit laengerem so. */
286
288
  (D ? Object.keys(D.patterns).sort() : []).forEach(function (m) {
287
- var o = opt(m, musterZeile(m));
288
- if (m === v.geraetetyp) { o.selected = true; }
289
+ var typ = D.patterns[m].type || m;
290
+ var o = opt(typ, musterZeile(m));
291
+ if (typ === v.geraetetyp) { o.selected = true; }
289
292
  st.appendChild(o);
290
293
  });
291
294
  st.addEventListener('change', function () {
@@ -543,7 +546,7 @@ function dtFeld(beschriftung, element) {
543
546
 
544
547
  /* Rollenauswahl samt Begruendung. geraetetyp darf leer sein — dann
545
548
  entfaellt nur der Hinweis, welchen Platz die Rolle traefe. */
546
- function rollenBlock(rolle, geraetetyp, setzen) {
549
+ function rollenBlock(rolle, geraetetyp, setzen, typJetzt, setzeTyp) {
547
550
  var rb = el('div');
548
551
  /* Dasselbe durchsuchbare Feld wie am Geraet - ein Aussehen fuer alle
549
552
  Rollenwahlen. Ohne Belegt-Vermerk: in der Vorlage gibt es keinen
@@ -553,24 +556,38 @@ function rollenBlock(rolle, geraetetyp, setzen) {
553
556
  muster: geraetetyp,
554
557
  leerErlaubt: true,
555
558
  breite: '300px',
556
- beiWahl: function (r) { setzen(r); }
559
+ /* Wie am Geraet: der Platz bringt seinen Datentyp mit, also wird er
560
+ beim Waehlen der Rolle gleich gesetzt. Eine Vorlage, deren Rolle
561
+ stimmt und deren Typ nicht, faellt sonst lautlos aus dem Muster. */
562
+ beiWahl: function (r) {
563
+ /* Wie am Geraet: nur ein eindeutiger Typ wird gesetzt. */
564
+ var moegliche = typenFuerRolle(geraetetyp, r);
565
+ if (moegliche.length === 1 && setzeTyp && typJetzt !== moegliche[0]) { setzeTyp(moegliche[0]); }
566
+ setzen(r);
567
+ }
557
568
  }));
558
569
 
559
570
  var must = geraetetyp && musterVon(geraetetyp);
560
571
  if (must) {
561
- var treffer = null;
562
- must.states.forEach(function (pp) {
563
- if (!treffer && pp.role && rolleTrifft(pp.role, rolle)) { treffer = pp; }
564
- });
572
+ var treffer = platzFuerRolle(geraetetyp, rolle, typJetzt);
573
+ var erlaubt = typenFuerRolle(geraetetyp, rolle);
565
574
  var hin = el('div', 'sugg');
566
575
  if (treffer) {
567
576
  hin.appendChild(document.createTextNode(tr('pattern.fitsOn')));
568
577
  hin.appendChild(el('b', null, String(treffer.role)));
569
578
  hin.appendChild(document.createTextNode(tr('pattern.toSlot', treffer.name, musterName(geraetetyp) || geraetetyp)));
579
+ if (erlaubt.length) {
580
+ hin.appendChild(document.createTextNode(
581
+ tr('pattern.expectsType', erlaubt.join(tr('pattern.typeOr')))));
582
+ }
570
583
  } else {
571
584
  hin.textContent = tr('pattern.noPlaceLong', musterName(geraetetyp) || geraetetyp);
572
585
  }
573
586
  rb.appendChild(hin);
587
+ if (treffer && typJetzt && erlaubt.length && erlaubt.indexOf(typJetzt) === -1) {
588
+ rb.appendChild(el('div', 'aside w',
589
+ tr('pattern.typeMismatch', treffer.name, erlaubt.join(tr('pattern.typeOr')), typJetzt)));
590
+ }
574
591
  }
575
592
  return rb;
576
593
  }
@@ -782,7 +799,7 @@ function vZeilenDetail(z, nr) {
782
799
 
783
800
  dtZeile(d, tr('detail.role'), rollenBlock(z.rolle, v.geraetetyp, function (neu) {
784
801
  setzeFeld('rolle', neu);
785
- }));
802
+ }, z.typ, function (t) { setzeFeld('typ', t); }));
786
803
 
787
804
  dtZeile(d, tr('detail.display'), anzeigeBlock(
788
805
  function (was) {
@@ -1222,9 +1239,24 @@ export function vorlagenVorbereiten(e) {
1222
1239
 
1223
1240
  var zeilen = [];
1224
1241
  e.states.forEach(function (s) {
1225
- if (!s.n || !s.srcR) { return; }
1226
- var r = relZu(s.srcR, e.kanal);
1227
- var w = (s.srcW && s.srcW !== s.srcR) ? relZu(s.srcW, e.kanal) : null;
1242
+ /* Eine Zeile braucht wenigstens ein Ziel - lesend oder schreibend.
1243
+ `STOP` am Rollladen hat nur ein Schreibziel: ein Tastendruck hat
1244
+ keinen Zustand zum Lesen. Bis 11.09.2026 stand hier `!s.srcR`,
1245
+ und solche Zeilen fielen beim Speichern lautlos weg. Dass das
1246
+ Anwenden diesen Fall kennt, steht seit dem 09.09.2026 in
1247
+ vorlagen.js - die Gegenrichtung war vergessen worden. */
1248
+ if (!s.n || (!s.srcR && !s.srcW)) { return; }
1249
+ var r = s.srcR ? relZu(s.srcR, e.kanal) : { p: '', abs: false };
1250
+ /* Das Schreibziel gehoert in die Vorlage, auch wenn es derselbe
1251
+ Punkt ist wie der gelesene. Bis 11.09.2026 stand hier zusaetzlich
1252
+ `s.srcW !== s.srcR`, und genau das trifft auf jeden Aktor zu, der
1253
+ seinen eigenen Zustand fuehrt: Rollladen, Dimmer, Thermostat
1254
+ schreiben auf denselben Punkt, den sie lesen (1.LEVEL). Beim
1255
+ Speichern verschwand ihr Schreibweg - der Alias zeigte die
1256
+ Position an und liess sich nicht mehr verstellen. Aufgefallen
1257
+ bei Tasmota nie, weil dort tele.STATE gelesen und cmnd.POWER
1258
+ geschrieben wird. */
1259
+ var w = s.srcW ? relZu(s.srcW, e.kanal) : null;
1228
1260
  var zv = null;
1229
1261
  if (quelle && s.ausVorlage) {
1230
1262
  (quelle.zustaende || []).forEach(function (z) { if (z.name === s.ausVorlage) { zv = z; } });
@@ -1348,9 +1380,11 @@ export function baueVorlage(z) {
1348
1380
  name: r.name,
1349
1381
  rolle: s.role || undefined,
1350
1382
  typ: s.typ || undefined,
1351
- einheit: s.unit || undefined,
1352
- lesen: zPfad(r, k, false, z.zeilen)
1383
+ einheit: s.unit || undefined
1353
1384
  };
1385
+ /* Ohne Lesequelle kein `lesen` - sonst stuende dort ein leerer
1386
+ Pfad, und `wendeAn` suchte einen Punkt namens "". */
1387
+ if (r.lesenRoh) { zu.lesen = zPfad(r, k, false, z.zeilen); }
1354
1388
  if (r.lesenAbs) { zu.absolut = true; }
1355
1389
  if (r.schreibenRoh) { zu.schreiben = zPfad(r, k, true, z.zeilen); }
1356
1390
  if (f) { zu.feld = f; } else if (s.f) { zu.leseformel = s.f; }
@@ -1696,9 +1730,16 @@ export function zeichneVorlagenDialog() {
1696
1730
  lt.appendChild(el('span', 'feldlabel', tr('tpls.deviceType')));
1697
1731
  var st = el('select', 'tx');
1698
1732
  st.appendChild(opt('', tr('tpls.noType')));
1733
+ /* Der Wert ist der Geraetetyp, nicht der Musterschluessel - drei
1734
+ Muster tragen einen anderen Namen als ihr Typ: blinds meldet
1735
+ "blind", mediaPlayer meldet "media", levelSlider meldet "slider".
1736
+ Die Vorlagen fuehren den Typ. Bis 11.09.2026 stand hier der
1737
+ Schluessel, und an jedem Rollladen zeigte der Dialog
1738
+ "- keiner -", obwohl "blind" gesetzt war. */
1699
1739
  (D ? Object.keys(D.patterns).sort() : []).forEach(function (m) {
1700
- var o = opt(m, musterZeile(m));
1701
- if (m === k.geraetetyp) { o.selected = true; }
1740
+ var typ = D.patterns[m].type || m;
1741
+ var o = opt(typ, musterZeile(m));
1742
+ if (typ === k.geraetetyp) { o.selected = true; }
1702
1743
  st.appendChild(o);
1703
1744
  });
1704
1745
  st.addEventListener('change', function () { k.geraetetyp = st.value; zeichneVorlagenDialog(); });
@@ -2017,19 +2058,81 @@ export function zeichneVorlagenDialog() {
2017
2058
 
2018
2059
  var b = $('#btn-tpl-save');
2019
2060
  if (b) {
2020
- b.disabled = fehlerErk || !k.id || !!fremdeMitId(k) ||
2021
- !hinweisTaugt(k.namenshinweis) ||
2022
- !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(' · ');
2023
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
+ }
2024
2084
  }
2025
2085
  var tt = $('#tpl-titel');
2026
2086
  if (tt) { tt.textContent = tr('tpls.title', z.e.kanal.split('.').pop()); }
2027
2087
  }
2028
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
+
2029
2115
  export function speichereVorlageAusDialog() {
2030
2116
  if (!S.tplZ) { return; }
2031
2117
  var neu = baueVorlage(S.tplZ);
2032
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
+
2033
2136
  /* Widerspricht sie sich, wird nicht gespeichert — eine Vorlage, die
2034
2137
  auf nichts passen kann, hilft niemandem und ist spaeter schwer zu
2035
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';