iobroker.alias-workbench 0.8.1 → 0.8.2

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.
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
2
+ <!-- hinten: die rohe Quelle, blasser Ton, Punkte ohne Ordnung -->
3
+ <rect x="4" y="4" width="74" height="74" rx="16" fill="#7CBCC1"/>
4
+ <circle cx="24.5" cy="27" r="6.2" fill="#fff" opacity=".8"/>
5
+ <circle cx="44" cy="36" r="6.2" fill="#fff" opacity=".8"/>
6
+ <circle cx="26" cy="56" r="6.2" fill="#fff" opacity=".8"/>
7
+ <!-- vorn: der fertige Alias, ausgerichtet und benannt -->
8
+ <rect x="46" y="46" width="78" height="78" rx="18" fill="#0E7C86"/>
9
+ <circle cx="67.5" cy="70" r="6.5" fill="#fff"/>
10
+ <rect x="82" y="64.5" width="27" height="11" rx="5.5" fill="#fff"/>
11
+ <circle cx="67.5" cy="100" r="6.5" fill="#fff"/>
12
+ <rect x="82" y="94.5" width="27" height="11" rx="5.5" fill="#fff"/>
13
+ </svg>
@@ -285,6 +285,43 @@
285
285
  border-bottom:1px solid var(--line); font-family:var(--mono); font-size:11.5px;
286
286
  }
287
287
  .detail .dr { display:grid; grid-template-columns:88px 1fr; gap:8px; padding:3px 0; align-items:start; }
288
+ /* Feld, an dem die Vorlage etwas anderes will. Dieselbe Farbe wie die
289
+ Marke an der zugeklappten Zeile — wer sie dort gesehen hat, sucht
290
+ hier nach ihr. Die dritte Spalte traegt den Uebernahme-Knopf; sie
291
+ bleibt schmal und leer, solange nichts abweicht. */
292
+ .detail .dr.abw { grid-template-columns:88px 1fr 26px;
293
+ background:color-mix(in srgb, var(--warn-soft) 70%, transparent);
294
+ border-radius:var(--r-sm); padding:3px 4px; margin:0 -4px; }
295
+ .detail .dr.abw .k { color:var(--warn); }
296
+ /* Der Wert der Vorlage, sichtbar unter dem Feld — er stand vorher nur
297
+ im Tooltip der Zeile und war damit hinter jedem Feld versteckt. */
298
+ .detail .tplwert { grid-column:2 / -1; font-size:10.5px; color:var(--warn);
299
+ font-family:var(--sans); padding-top:1px; }
300
+ .detail .tplwert b { font-family:var(--mono); font-weight:600; }
301
+ /* Die Abweichung vom gespeicherten Alias — dieselbe Bauart wie die
302
+ Vorlagenabweichung, aber in der Akzentfarbe statt in der Warnfarbe:
303
+ Was in der Datenbank steht, ist kein Missstand, sondern der Stand,
304
+ mit dem man vergleicht. Eine Zeile kann beides tragen. */
305
+ .detail .dr.bstabw { grid-template-columns:88px 1fr 26px;
306
+ background:color-mix(in srgb, var(--accent) 8%, transparent);
307
+ border-radius:var(--r-sm); padding:3px 4px; margin:0 -4px; }
308
+ .detail .dr.abw.bstabw { background:linear-gradient(
309
+ to right,
310
+ color-mix(in srgb, var(--warn-soft) 70%, transparent) 0 50%,
311
+ color-mix(in srgb, var(--accent) 8%, transparent) 50% 100%); }
312
+ .detail .bstwert { grid-column:2 / -1; font-size:10.5px; color:var(--accent);
313
+ font-family:var(--sans); padding-top:1px; }
314
+ .detail .bstwert b { font-family:var(--mono); font-weight:600; }
315
+ .detail .bstnimm { align-self:center; border-color:var(--accent); color:var(--accent);
316
+ background:transparent; cursor:pointer; }
317
+ .detail .bstnimm:hover { background:var(--accent); color:var(--surface); }
318
+ .erow .geae.bstabw { border-color:var(--accent); color:var(--accent);
319
+ background:color-mix(in srgb, var(--accent) 10%, transparent); }
320
+ .btn.winzig { font-size:11px; line-height:1; padding:3px 6px; min-width:0;
321
+ border-radius:var(--r-sm); }
322
+ .detail .abwnimm { align-self:center; border-color:var(--warn); color:var(--warn);
323
+ background:transparent; cursor:pointer; }
324
+ .detail .abwnimm:hover { background:var(--warn); color:var(--surface); }
288
325
  .detail .k {
289
326
  font-family:var(--sans); font-size:9px; text-transform:uppercase; letter-spacing:.06em;
290
327
  color:var(--ink-3); font-weight:700; padding-top:3px;
@@ -444,6 +481,28 @@
444
481
  /* Abgewaehlt heisst ausgegraut, nicht durchgestrichen - der Haken
445
482
  fehlt ja sichtbar, und durchgestrichen liest sich der Name schlecht. */
446
483
  .erow.skip { opacity:.42; }
484
+ /* Angehakt, aber ohne Platz im Muster: die Zeile bekommt einen Ton,
485
+ statt in jeder einzelnen denselben Satz zu wiederholen.
486
+
487
+ Gedaempft, nicht laut: der Punkt wird ja angelegt und ist voll
488
+ benutzbar — nur Alexa & Co. zaehlen ihn separat. Das ist eine
489
+ Auskunft, keine Warnung. Ueber `transparent` gemischt, damit
490
+ derselbe Anteil in hell wie in dunkel traegt.
491
+
492
+ Der Hover braucht eine eigene Zeile: sonst gewaenne bei gleicher
493
+ Spezifitaet die spaeter stehende Regel, und die Zeile reagierte
494
+ nicht mehr auf den Zeiger. */
495
+ .erow.ohneplatz { background:color-mix(in srgb, var(--warn-soft) 80%, transparent); }
496
+ .erow.ohneplatz:hover, .erow.ohneplatz.open {
497
+ background:color-mix(in srgb, var(--warn-soft) 100%, transparent); }
498
+ .listbar .legende { display:inline-flex; align-items:center; gap:5px;
499
+ font-size:11px; color:var(--ink-3); white-space:nowrap; }
500
+ /* Dasselbe Feld wie die Zeile, nur klein — die Legende zeigt die Farbe,
501
+ nach der man in der Liste sucht. */
502
+ .listbar .legende .lgmarke { width:14px; height:11px; border-radius:3px;
503
+ border:1px solid var(--line);
504
+ background:color-mix(in srgb, var(--warn-soft) 80%, transparent);
505
+ display:inline-block; }
447
506
  .erow input[type="checkbox"] { accent-color:var(--accent); cursor:pointer; margin:0; }
448
507
  .erow .mk2 { text-align:center; font-size:11px; }
449
508
  .erow .mk2.ok { color:var(--ok); } .erow .mk2.bad { color:var(--bad); } .erow .mk2.out { color:var(--ink-3); }
@@ -775,6 +834,16 @@
775
834
  background:color-mix(in srgb, var(--accent) 14%, transparent);
776
835
  }
777
836
  }
837
+ /* Der Zwilling-Hinweis: es gibt schon einen Alias auf diese Quelle.
838
+
839
+ Das ist keine Frage wie das Raum-Angebot daneben, sondern eine
840
+ Auskunft - deshalb kein Pochen, sondern die ruhige Warnfarbe. Sie
841
+ haelt niemanden auf: der Knopf zum Anlegen bleibt, wo er war. */
842
+ .uebernahme.zwilling .zq {
843
+ color:var(--warn); font-weight:600;
844
+ background:color-mix(in srgb, var(--warn) 12%, transparent);
845
+ }
846
+
778
847
  .enumbar .enumfeld { padding-right:22px; }
779
848
 
780
849
  /* Trockenlauf: eine Zeile je Objekt, das JSON aufklappbar darunter. */
@@ -45,6 +45,8 @@
45
45
  "del.now": "Endgültig entfernen",
46
46
  "del.objects": "Was verschwindet",
47
47
  "del.title": "Alias entfernen",
48
+ "detail.aliasEmpty": "Im Alias ist dieses Feld leer.",
49
+ "detail.aliasHas": "Im Alias steht: ",
48
50
  "detail.asDelivered": "wie geliefert",
49
51
  "detail.attention": "Achtung",
50
52
  "detail.caption": "Beschriftung",
@@ -80,6 +82,10 @@
80
82
  "detail.separateHint": "getrennt — der Schalter zeigt den Zustand, nicht den Befehl",
81
83
  "detail.sourceGone": "Quelle existiert nicht",
82
84
  "detail.stateSays": "Zustand sagt {0}",
85
+ "detail.takeFromAlias": "Diesen einen Wert aus dem gespeicherten Alias zurückholen",
86
+ "detail.takeOne": "Diesen einen Wert aus der Vorlage übernehmen",
87
+ "detail.tplWants": "Die Vorlage will hier: ",
88
+ "detail.tplWantsEmpty": "Die Vorlage lässt dieses Feld leer.",
83
89
  "detail.type": "Typ",
84
90
  "detail.unit": "Einheit",
85
91
  "detail.willBecome": "entsteht als",
@@ -160,7 +166,12 @@
160
166
  "info.explain": "funktionieren ganz normal und liegen da, wo du sie angelegt hast. Es entsteht dadurch kein zusätzliches Objekt — der Alias bleibt, wie er ist. Sie gehören nur nicht zu dem, was ioBroker unter einem {0} versteht; dafür passt ihre Rolle nicht. Bedeutung hat das erst, wenn du den Alias an Alexa, Google Home oder eine Gerätesteuerung hängst: die sehen dann ein {0} ohne diese Punkte. In VIS, in Skripten und im Verlauf benutzt du sie wie jeden anderen Datenpunkt.",
161
167
  "info.notPartOf": "Gehört nicht zum {0}",
162
168
  "info.sameDatapoints": "{0} — ",
169
+ "leave.body": "An {0} sind Änderungen offen, die noch nicht geschrieben wurden. Wechselst du jetzt, sind sie weg.",
170
+ "leave.discard": "Verwerfen und wechseln",
171
+ "leave.stay": "Hierbleiben",
172
+ "leave.title": "Ungespeicherte Änderungen",
163
173
  "list.addByHand": "Datenpunkt von Hand hinzufügen",
174
+ "list.aliasDiffers": "weicht vom gespeicherten Alias ab",
164
175
  "list.all": "alle",
165
176
  "list.byHand": "von Hand",
166
177
  "list.changed": "geändert",
@@ -181,7 +192,7 @@
181
192
  "list.sendPointMissing": "Sendepunkt fehlt noch",
182
193
  "list.sourceMissing": "Quelle fehlt",
183
194
  "list.tpl": "Vorlage",
184
- "list.tplDiffers": "Vorlage: {0}",
195
+ "list.tplDiffers": "weicht von der Vorlage ab",
185
196
  "list.tplHint": "Hakt genau die Punkte an, die die Vorlage vorschlägt — unabhängig davon, was heute im Alias steht.",
186
197
  "list.willBeCreated": "angelegt werden",
187
198
  "move.button": "Verlegen …",
@@ -327,8 +338,11 @@
327
338
  "target.folder": "Ablegen unter",
328
339
  "target.folderHint": "Unterordner mit Punkt trennen, z. B. Haus.EG.Küche",
329
340
  "target.folderPlaceholder": "leer = direkt unter alias.0",
341
+ "target.moveInstead": "stattdessen verlegen …",
342
+ "target.moveInsteadHint": "Den vorhandenen Alias auf die neue Kennung verlegen, statt einen zweiten daneben anzulegen.",
330
343
  "target.name": "Name",
331
344
  "target.openFolder": "„{0}\" öffnen",
345
+ "target.twinExists": "Für diese Quelle gibt es schon {0} — der bleibt stehen.",
332
346
  "target.yes": "ja",
333
347
  "tpl.applyTemplate": "Werte der Vorlage übernehmen ({0})",
334
348
  "tpl.applyTemplateHint": "Setzt Rollen, Typen, Einheiten, Formeln und Quellen auf den Stand der Vorlage. Die Beschriftung bleibt, wie sie ist — die gehört dir. Geschrieben wird erst nach dem Trockenlauf.",
@@ -45,6 +45,8 @@
45
45
  "del.now": "Remove for good",
46
46
  "del.objects": "What disappears",
47
47
  "del.title": "Remove alias",
48
+ "detail.aliasEmpty": "The alias leaves this field empty.",
49
+ "detail.aliasHas": "The alias has: ",
48
50
  "detail.asDelivered": "as delivered",
49
51
  "detail.attention": "Attention",
50
52
  "detail.caption": "Caption",
@@ -80,6 +82,10 @@
80
82
  "detail.separateHint": "separate — the switch shows the state, not the command",
81
83
  "detail.sourceGone": "source does not exist",
82
84
  "detail.stateSays": "state says {0}",
85
+ "detail.takeFromAlias": "Take this one value back from the stored alias",
86
+ "detail.takeOne": "Take this one value from the template",
87
+ "detail.tplWants": "The template wants: ",
88
+ "detail.tplWantsEmpty": "The template leaves this field empty.",
83
89
  "detail.type": "Type",
84
90
  "detail.unit": "Unit",
85
91
  "detail.willBecome": "will become",
@@ -160,7 +166,12 @@
160
166
  "info.explain": "work perfectly normally and stay where you created them. Nothing extra is written — the alias stays as it is. They simply are not part of what ioBroker considers a {0}; their role does not fit any of its slots. It only starts to matter once you hand the alias to Alexa, Google Home or a device UI: those will see a {0} without these points. In VIS, in scripts and in history you use them like any other datapoint.",
161
167
  "info.notPartOf": "Not part of the {0}",
162
168
  "info.sameDatapoints": "{0} — ",
169
+ "leave.body": "There are changes on {0} that have not been written yet. Switching now discards them.",
170
+ "leave.discard": "Discard and switch",
171
+ "leave.stay": "Stay here",
172
+ "leave.title": "Unsaved changes",
163
173
  "list.addByHand": "Add datapoint by hand",
174
+ "list.aliasDiffers": "differs from the stored alias",
164
175
  "list.all": "all",
165
176
  "list.byHand": "by hand",
166
177
  "list.changed": "changed",
@@ -181,7 +192,7 @@
181
192
  "list.sendPointMissing": "send datapoint not created yet",
182
193
  "list.sourceMissing": "source missing",
183
194
  "list.tpl": "template",
184
- "list.tplDiffers": "template: {0}",
195
+ "list.tplDiffers": "differs from the template",
185
196
  "list.tplHint": "Checks exactly the points the template proposes — regardless of what the alias holds today.",
186
197
  "list.willBeCreated": "will be created:",
187
198
  "move.button": "Move …",
@@ -327,8 +338,11 @@
327
338
  "target.folder": "Place under",
328
339
  "target.folderHint": "separate subfolders with a dot, e.g. House.GF.Kitchen",
329
340
  "target.folderPlaceholder": "empty = directly under alias.0",
341
+ "target.moveInstead": "move it instead …",
342
+ "target.moveInsteadHint": "Move the existing alias to the new id instead of creating a second one next to it.",
330
343
  "target.name": "Name",
331
344
  "target.openFolder": "open “{0}”",
345
+ "target.twinExists": "This source already has {0} — that one stays where it is.",
332
346
  "target.yes": "yes",
333
347
  "tpl.applyTemplate": "take template values ({0})",
334
348
  "tpl.applyTemplateHint": "Sets roles, types, units, formulas and sources to what the template says. The caption stays as it is — that one is yours. Nothing is written before the dry run.",
package/admin/js/baum.js CHANGED
@@ -8,7 +8,7 @@ import { S } from './zustand.js';
8
8
  import { $, el } from './basis.js';
9
9
  import { tr, txt, expertenVorgabe } from './sprache.js';
10
10
  import './enums.js';
11
- import { waehle } from './entwurf.js';
11
+ import { mitNachfrage } from './entwurf.js';
12
12
  import { zeichneErgebnis } from './ergebnis.js';
13
13
  import { zeichneVorlagenListe } from './vorlagenblatt.js';
14
14
 
@@ -354,7 +354,17 @@ function renderKinder(knoten, filter, tiefe) {
354
354
  if (waehlbar) {
355
355
  d.tabIndex = 0;
356
356
  d.setAttribute('role', 'button');
357
- var go = function () { waehle(k.id); };
357
+ /* Erst fragen, dann wechseln — solange am aktuellen Entwurf etwas
358
+ offen ist. Vorher fielen Aenderungen bei einem Klick ins Leere,
359
+ ohne ein Wort (Ricardo, 06.09.2026): Rolle geaendert, im Baum
360
+ weitergeklickt, alles weg.
361
+
362
+ Die Frage haengt an `angefasst`, nicht an `geaendert` einer
363
+ einzelnen Zeile: Auch Raum, Funktion, Ordner und Name gehoeren
364
+ dazu, und die stehen nicht in den Zeilen. Geschrieben wird
365
+ dadurch nichts — es geht nur darum, dass niemand etwas verliert,
366
+ ohne gefragt zu werden. */
367
+ var go = function () { mitNachfrage(k.id); };
358
368
  d.addEventListener('click', go);
359
369
  d.addEventListener('keydown', function (e) {
360
370
  if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); go(); }
@@ -8,7 +8,7 @@ import { wertVon, jsonFelder, feldAusFormel, feldFormel, feldWert, jsonVon } fro
8
8
  import { musterVon, erkenneEntwurf } from './erkennung.js';
9
9
  import { musterName } from './musternamen.js';
10
10
  import { rollenFeld } from './rollenwahl.js';
11
- import { opt , quellenAuswahl } from './entwurf.js';
11
+ import { opt , quellenAuswahl, vorlagenAbweichung, bestandsAbweichung } from './entwurf.js';
12
12
  import { zeichneErgebnis, entwurfAngefasst } from './ergebnis.js';
13
13
 
14
14
  import { rateBehalten } from './vorschlagen.js';
@@ -28,6 +28,118 @@ export function detailZeile(e, s, idx) {
28
28
  keine mehr und faellt beim Zuruecknehmen nicht mehr weg. */
29
29
  function neu() { s.geaendert = true; rateBehalten(s); entwurfAngefasst(); zeichneErgebnis(); }
30
30
 
31
+ /* ---- Wo die Vorlage etwas anderes will --------------------------
32
+
33
+ Die zugeklappte Zeile sagt nur, DASS etwas abweicht. Welches Feld,
34
+ steht hier: die betroffene Zeile wird getoent, ihr Tooltip nennt den
35
+ Wert der Vorlage, und ein Knopf daneben uebernimmt genau diesen
36
+ einen Wert (Ricardo, 06.09.2026).
37
+
38
+ Vorher gab es die Uebernahme nur als Sammelaktion ueber der Liste —
39
+ alles oder nichts. Wer eine Rolle angleichen wollte, ohne die
40
+ Formeln mitzunehmen, musste tippen.
41
+
42
+ `geaendert` wird dabei ABSICHTLICH nicht gesetzt: Sonst faellt die
43
+ Marke an der Zeile weg (sie haengt an `!s.geaendert`), und die
44
+ restlichen Abweichungen waeren unsichtbar. Wer alle uebernimmt,
45
+ dessen Marke verschwindet von selbst — dann weicht ja nichts mehr
46
+ ab. */
47
+ var abwListe = vorlagenAbweichung(s);
48
+ var abwNach = {};
49
+ abwListe.forEach(function (x) { abwNach[x.feld] = x; });
50
+
51
+ function zeigeAbweichung(r, felder) {
52
+ if (!r) { return; }
53
+ var treffer = felder.filter(function (f) { return abwNach[f]; });
54
+ if (!treffer.length) { return; }
55
+ r.classList.add('abw');
56
+
57
+ var b = el('button', 'btn winzig abwnimm', '←');
58
+ b.addEventListener('click', function (ev) {
59
+ ev.stopPropagation();
60
+ var v = s.vorlagenWert;
61
+ if (!v) { return; }
62
+ treffer.forEach(function (f) { s[f] = v[f]; });
63
+ /* Uebernehmen ist eine Aenderung wie jede andere — die Zeile traegt
64
+ danach „geaendert". Dass die Marke „weicht von der Vorlage ab"
65
+ dabei nicht verschwindet, sorgt die Zeichenseite: sie haengt
66
+ seit dem 06.09.2026 daran, ob wirklich noch etwas abweicht, und
67
+ nicht mehr an `!geaendert`. Sonst waeren nach der ersten
68
+ Uebernahme die uebrigen Abweichungen unsichtbar. */
69
+ s.geaendert = true;
70
+ rateBehalten(s);
71
+ entwurfAngefasst();
72
+ zeichneErgebnis();
73
+ });
74
+ r.appendChild(b);
75
+
76
+ /* Der Wert der Vorlage gehoert HINGESCHRIEBEN, nicht in einen
77
+ Tooltip: der hing an der Zeile, und sobald der Zeiger ueber dem
78
+ Auswahlfeld oder dem Knopf stand, zeigte der Browser deren
79
+ Tooltip. Man musste die getoente Flaeche danebentreffen, um zu
80
+ erfahren, was man da uebernimmt (Ricardo, 06.09.2026). */
81
+ var hin = el('div', 'tplwert');
82
+ treffer.forEach(function (f, i) {
83
+ if (i) { hin.appendChild(document.createTextNode(' ')); }
84
+ var v = abwNach[f].vorlage;
85
+ hin.appendChild(document.createTextNode(
86
+ v ? tr('detail.tplWants', '') : tr('detail.tplWantsEmpty')));
87
+ if (v) { hin.appendChild(el('b', null, String(v))); }
88
+ });
89
+ b.title = tr('detail.takeOne');
90
+ r.appendChild(hin);
91
+ }
92
+
93
+ /* Dasselbe gegen den gespeicherten Alias.
94
+
95
+ Getrennt von der Vorlagenabweichung, weil es eine andere Aussage ist:
96
+ dort steht, was die Vorlage wollte, hier, was in der Datenbank steht.
97
+ Eine Zeile kann beides tragen — etwa nach einem Musterwechsel, der
98
+ die Rolle anpasst: Die neue Rolle weicht dann vom Alias ab, und je
99
+ nachdem auch von der Vorlage (Ricardo, 06.09.2026). */
100
+ var bstListe = bestandsAbweichung(s, e.ziel);
101
+ var bstNach = {};
102
+ bstListe.forEach(function (x) { bstNach[x.feld] = x; });
103
+
104
+ function zeigeBestand(r, felder) {
105
+ if (!r) { return; }
106
+ var treffer = felder.filter(function (f) { return bstNach[f]; });
107
+ if (!treffer.length) { return; }
108
+ r.classList.add('bstabw');
109
+
110
+ var b2 = el('button', 'btn winzig bstnimm', '⟲');
111
+ b2.title = tr('detail.takeFromAlias');
112
+ b2.addEventListener('click', function (ev) {
113
+ ev.stopPropagation();
114
+ treffer.forEach(function (f) { s[f] = bstNach[f].bestand; });
115
+ /* Zurueckholen ist eine Entscheidung wie das Uebernehmen aus der
116
+ Vorlage — die Zeile traegt danach „geaendert". Die Marke bleibt,
117
+ solange noch etwas abweicht; sie haengt nicht an `geaendert`. */
118
+ s.geaendert = true;
119
+ rateBehalten(s);
120
+ entwurfAngefasst();
121
+ zeichneErgebnis();
122
+ });
123
+ r.appendChild(b2);
124
+
125
+ var hin2 = el('div', 'bstwert');
126
+ treffer.forEach(function (f, i) {
127
+ if (i) { hin2.appendChild(document.createTextNode(' ')); }
128
+ var v = bstNach[f].bestand;
129
+ hin2.appendChild(document.createTextNode(
130
+ v ? tr('detail.aliasHas', '') : tr('detail.aliasEmpty')));
131
+ if (v) { hin2.appendChild(el('b', null, String(v))); }
132
+ });
133
+ r.appendChild(hin2);
134
+ }
135
+
136
+ /* Beide Auskuenfte an derselben Zeile: erst was die Vorlage will,
137
+ dann was im Alias steht. */
138
+ function zeigeBeide(r, felder) {
139
+ zeigeAbweichung(r, felder);
140
+ zeigeBestand(r, felder);
141
+ }
142
+
31
143
  /* --- Name, nur bei selbst angelegten --- */
32
144
  if (s.manuell) {
33
145
  var iN = el('input', 'tx w');
@@ -103,7 +215,7 @@ export function detailZeile(e, s, idx) {
103
215
  lF.appendChild(selF);
104
216
  qf.appendChild(lF);
105
217
  }
106
- zeile(tr('detail.readsFrom'), qf);
218
+ zeigeBeide(zeile(tr('detail.readsFrom'), qf), ['srcR']);
107
219
 
108
220
  if (s.frei) {
109
221
  var iFrei = el('input', 'tx w');
@@ -143,7 +255,7 @@ export function detailZeile(e, s, idx) {
143
255
  if (s.srcW && s.srcW !== s.srcR) {
144
256
  wf.appendChild(el('div', 'sugg', tr('detail.separateHint')));
145
257
  }
146
- zeile(tr('detail.writesTo'), wf);
258
+ zeigeBeide(zeile(tr('detail.writesTo'), wf), ['srcW']);
147
259
 
148
260
  /* --- Leseformel --- */
149
261
  var fb = el('div');
@@ -159,7 +271,7 @@ export function detailZeile(e, s, idx) {
159
271
  if ((s.typ || '') === 'boolean') {
160
272
  fb.appendChild(el('div', 'sugg', tr('detail.formulaHint')));
161
273
  }
162
- zeile(tr('detail.formula'), fb);
274
+ zeigeBeide(zeile(tr('detail.formula'), fb), ['f']);
163
275
 
164
276
  if (s.srcW) {
165
277
  var wb = el('div');
@@ -171,7 +283,7 @@ export function detailZeile(e, s, idx) {
171
283
  iW.addEventListener('change', function () { s.fw = iW.value.trim(); neu(); });
172
284
  wb.appendChild(iW);
173
285
  wb.appendChild(el('div', 'sugg', tr('detail.writeFormulaHint')));
174
- zeile(tr('detail.writeFormula'), wb);
286
+ zeigeBeide(zeile(tr('detail.writeFormula'), wb), ['fw']);
175
287
  }
176
288
 
177
289
  /* --- Rolle, mit Begründung --- */
@@ -211,7 +323,7 @@ export function detailZeile(e, s, idx) {
211
323
  }
212
324
  rb.appendChild(hin);
213
325
  }
214
- zeile(tr('detail.role'), rb);
326
+ zeigeBeide(zeile(tr('detail.role'), rb), ['role']);
215
327
 
216
328
  /* --- Typ und Anzeige --- */
217
329
  var af = el('div', 'fields');
@@ -268,7 +380,7 @@ export function detailZeile(e, s, idx) {
268
380
  lC.appendChild(el('span', 'sugg', tr('detail.captionHint')));
269
381
  af.appendChild(lC);
270
382
 
271
- zeile(tr('detail.display'), af);
383
+ zeigeBeide(zeile(tr('detail.display'), af), ['typ', 'unit']);
272
384
 
273
385
  /* --- Rohwert und Ergebnis --- */
274
386
  var a = wertVon(s);
@@ -5,7 +5,7 @@
5
5
  import { S } from './zustand.js';
6
6
  import { socket } from './verbindung.js';
7
7
  import { $ } from './basis.js';
8
- import { txt } from './sprache.js';
8
+ import { txt, tr } from './sprache.js';
9
9
  import './enums.js';
10
10
  import { zusatzName, setzeModus, zeichneBaum, merkeKlappstand } from './baum.js';
11
11
  import { kindZustaende, aliasQuellen, holeWerte } from './werte.js';
@@ -251,14 +251,56 @@ export function quelleVon(aliasId) {
251
251
  return knotenDa(beste) ? beste : null;
252
252
  }
253
253
 
254
+ /* Vor dem Wechsel fragen, wenn am Entwurf etwas offen ist.
255
+
256
+ Drei Wege wechseln die Auswahl auf Wunsch des Nutzers, und alle drei
257
+ gehen hier durch: der Klick im Baum, „zur Quelle →" / „zum Alias →"
258
+ und die Zeilen der Ordneruebersicht. Vorher fielen Aenderungen dabei
259
+ ins Leere, ohne ein Wort (Ricardo, 06.09.2026).
260
+
261
+ Nicht hier durch gehen die Wechsel, die die Werkbank selbst ausloest:
262
+ nach dem Schreiben, nach dem Verlegen, nach dem Quellentausch. Dort
263
+ ist der Entwurf gerade geschrieben worden, es gibt nichts zu retten.
264
+ Ebenso wenig der Knopf „zuruecksetzen" — der WILL verwerfen, und wer
265
+ ihn drueckt, hat die Frage schon beantwortet.
266
+
267
+ Sie haengt an `angefasst`, nicht an `geaendert` einer einzelnen Zeile:
268
+ Raum, Funktion, Ordner und Name gehoeren genauso dazu und stehen in
269
+ keiner Zeile. */
270
+ export function mitNachfrage(id, dann) {
271
+ var lauf = function () { if (dann) { dann(); } else { waehle(id); } };
272
+ var e = S.entwurf;
273
+ if (!e || !e.angefasst || id === S.current) { lauf(); return; }
274
+
275
+ var dlg = $('#dlg-leave');
276
+ if (!dlg) { lauf(); return; } /* aeltere Fassung: nicht blockieren */
277
+
278
+ var was = (e.ziel || e.kanal || S.current || '').split('.').slice(-2).join('.');
279
+ $('#leave-titel').textContent = tr('leave.title');
280
+ $('#leave-body').textContent = tr('leave.body', was);
281
+
282
+ var bleib = $('#btn-leave-stay');
283
+ var weiter = $('#btn-leave-go');
284
+ bleib.textContent = tr('leave.stay');
285
+ weiter.textContent = tr('leave.discard');
286
+
287
+ /* Bei jedem Aufruf neu verkabeln — sonst haengt der Handler des
288
+ vorigen Aufrufs mit dem alten Ziel daran. */
289
+ bleib.onclick = function () { dlg.close(); };
290
+ weiter.onclick = function () { dlg.close(); lauf(); };
291
+ dlg.showModal();
292
+ }
293
+
254
294
  /* Der Sprung selbst: Baummodus umstellen, Filter fallen lassen, waehlen.
255
295
  Ohne das Leeren des Filters landet man auf einem Knoten, den der
256
296
  Baum gerade ausblendet. */
257
297
  export function springeZu(id, modus) {
258
- var q0 = $('#q');
259
- if (q0) { q0.value = ''; }
260
- if (S.baumModus !== modus) { setzeModus(modus); }
261
- waehle(id);
298
+ mitNachfrage(id, function () {
299
+ var q0 = $('#q');
300
+ if (q0) { q0.value = ''; }
301
+ if (S.baumModus !== modus) { setzeModus(modus); }
302
+ waehle(id);
303
+ });
262
304
  }
263
305
 
264
306
  /* Was in `common` der Werkbank gehoert - und was nicht.
@@ -868,6 +910,52 @@ export function bestandVorrang(e, aliasId) {
868
910
  return e;
869
911
  }
870
912
 
913
+ /* Wo weicht der Entwurf vom gespeicherten Alias ab?
914
+
915
+ Das ist die Gegenrichtung zu `vorlagenAbweichung`: Dort wird gegen das
916
+ verglichen, was die Vorlage will, hier gegen das, was schon in der
917
+ Datenbank steht.
918
+
919
+ Der Fall, fuer den es gebaut ist: Wer das Muster oder die Vorlage
920
+ wechselt, bekommt die Rollen automatisch angepasst — `switch.light`
921
+ passt auf kein socket-SET, also baut die Werkbank sie um. Das ist
922
+ richtig so, nur sah man an der Zeile nicht, dass damit etwas anderes
923
+ geschrieben wuerde als bisher dasteht. Sichtbar war es allein an den
924
+ Sammelchips oben und im Trockenlauf (Ricardo, 06.09.2026).
925
+
926
+ Verglichen wird gegen das Objekt, nicht gegen eine Kopie: Nach einem
927
+ Vorlagenwechsel traegt die Zeile den Vorlagenstand, und `vorlagenWert`
928
+ haelt genau denselben — eine Kopie brachte hier also nichts. */
929
+ export function bestandsAbweichung(s, aliasId) {
930
+ if (!s || !s.n || !s.on || !aliasId) { return []; }
931
+ var o = S.objects[aliasId + '.' + s.n];
932
+ if (!o || !o.common) { return []; }
933
+ var c = o.common, a = c.alias || {}, q = aliasQuellen(o);
934
+ var ist = {
935
+ role: c.role || '', typ: c.type || '', unit: c.unit || '',
936
+ f: (typeof a.read === 'string') ? a.read : '',
937
+ fw: (typeof a.write === 'string') ? a.write : '',
938
+ srcR: q.read || '',
939
+ srcW: q.einfach
940
+ ? ((c.write === true || typeof a.write === 'string') ? (q.write || '') : '')
941
+ : (q.write || '')
942
+ };
943
+ var raus = [];
944
+ ['role', 'typ', 'unit', 'f', 'fw', 'srcR', 'srcW'].forEach(function (k) {
945
+ var jetzt = s[k] || '';
946
+ if (jetzt !== ist[k]) { raus.push({ feld: k, jetzt: jetzt, bestand: ist[k] }); }
947
+ });
948
+ return raus;
949
+ }
950
+
951
+ /* Einen einzelnen Wert aus dem gespeicherten Alias zurueckholen. */
952
+ export function bestandsWert(s, aliasId, feld) {
953
+ var abw = bestandsAbweichung(s, aliasId);
954
+ var tr0 = null;
955
+ abw.forEach(function (x) { if (x.feld === feld) { tr0 = x; } });
956
+ return tr0 ? tr0.bestand : null;
957
+ }
958
+
871
959
  /* Wo weicht die Vorlage vom Bestand ab? Liefert je Zeile die Felder, die
872
960
  „Vorlage anwenden" aendern wuerde - die Beschriftung ausgenommen. */
873
961
  export function vorlagenAbweichung(s) {
@@ -24,6 +24,7 @@ import {
24
24
  quelleVon,
25
25
  springeZu,
26
26
  waehle,
27
+ mitNachfrage,
27
28
  geraeteDarunter,
28
29
  aliasFuer,
29
30
  tipptGerade,
@@ -148,7 +149,10 @@ var warumAuf = false;
148
149
 
149
150
  (function () {
150
151
  var b = $('#btn-move');
151
- if (b) { b.addEventListener('click', zeigeVerlegen); }
152
+ /* Nicht direkt anhaengen: `zeigeVerlegen` nimmt seit dem Knopf
153
+ „stattdessen verlegen" zwei Vorgaben entgegen, und der Zuhoerer
154
+ wuerde ihm das Klickereignis als erste durchreichen. */
155
+ if (b) { b.addEventListener('click', function () { zeigeVerlegen(); }); }
152
156
  var bt = $('#btn-swap');
153
157
  if (bt) { bt.addEventListener('click', zeigeTausch); }
154
158
  var btg = $('#btn-swap-go');
@@ -513,7 +517,16 @@ var warumAuf = false;
513
517
  S.entwurf = v;
514
518
  S.openRow = null;
515
519
  zeichneErgebnis();
520
+ return;
516
521
  }
522
+ /* Kommt kein Entwurf zurueck, bleibt die alte Vorlage in Kraft — das
523
+ Feld darf dann nicht die neue anzeigen. Bis 06.09.2026 fehlte
524
+ dieser Zweig: Der Wechsel scheiterte still, und wer das Feld las,
525
+ glaubte, die Vorlage sei gewechselt (Ricardo). Seit derselben
526
+ Aenderung entsteht ein Entwurf auch ohne jeden Treffer, der Fall
527
+ ist also selten geworden — aber nicht unmoeglich. */
528
+ selV.value = e.vorlage || '';
529
+ zeichneErgebnis();
517
530
  });
518
531
  lv.appendChild(selV);
519
532
  acts.appendChild(lv);
@@ -862,7 +875,7 @@ var warumAuf = false;
862
875
  row.appendChild(al);
863
876
  row.appendChild(el('span', 'ca2', '›'));
864
877
 
865
- var geh = function () { waehle(g.id); };
878
+ var geh = function () { mitNachfrage(g.id); };
866
879
  row.addEventListener('click', geh);
867
880
  row.addEventListener('keydown', function (ev) {
868
881
  if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); geh(); }