iobroker.alias-workbench 0.9.9 → 0.9.11

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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Ricardo Hipp
3
+ Copyright (c) 2026 Ricardo Hipp <ricardo.hipp@googlemail.com>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -564,6 +564,35 @@ imported, and that both language files carry the same keys.
564
564
 
565
565
  ## Changelog
566
566
 
567
+ ### 0.9.11 (2026-09-16)
568
+ * After saving, the next click in the tree asked **“unsaved changes”** — about an alias that had just been written. The “something unwritten is here” mark was only ever set, never cleared; it went away solely when the draft itself was dropped, i.e. on delete and on move. It now falls after every successful write — and only then: if something went wrong, the change really is not in yet, and the question stays justified. **There is a second effect that had been missing for a long time**: while the mark stood, the workbench deliberately did not rebuild the draft. After a write the old one therefore stayed, instead of rebuilding from the freshly written alias.
569
+ * A second press of **“Suggest free slots”** bent back a row you had set yourself. On the air conditioner: guess nine slots, set the role of the SPEED row to `level.speed` by hand, discard — and the next suggestion put the guessed `level.mode.fan` back. The pattern slot looked free, so the same row was guessed a second time; it then carried **both** marks, “changed” and “guessed”, which exclude each other — what was just guessed cannot have been changed before. Guessing now leaves any row marked “changed” alone. The slot stays free, and the note below says why. Putting a second row next to it was the alternative — the same source point would then hold two slots, and the eighth check would rightly report it.
570
+ * As soon as guesses were pending, the button in the pattern bar renamed itself to **“Discard suggestions”** — and a second one with the same word and the same effect stood in the blue bar below it. Two buttons, one handle. The bar now always says “Suggest free slots”, and only while something is left to guess; once everything is guessed it disappears. Discarding happens where it belongs: in the blue bar, next to “keep all”.
571
+ * Instead of „either every slot is taken, or no remaining datapoint fits" the note now states the case that actually applies — with the **names** of the slots that stay free: “SPEED, SWING … stays free. Every remaining datapoint already serves as a read or write source in a filled row”, or “… No remaining datapoint fits”. If a hand decision holds a slot open, a sentence is appended: “1 rows were decided by hand and are left untouched.” The “every slot taken” case stays silent unless asked — it says “nothing to do”, and would otherwise appear on nearly every device.
572
+ * Ricardo’s air conditioner is detected as a **thermostat** and fills 3 of 24 slots. **airCondition** fails because its only required slot, MODE, does not carry the role — and that settles it, since “does not fit” has the last word. With automatic suggestions it would reach **9 of 29** against 8 of 24. That now stands next to the pattern field: “· airCondition might fit better” — a single line, both numbers in the tooltip. **One** pattern is named — the best one — and only when it really is better; measured in filled slots, not in ratio. Next to it sits a small **→** button that does both in one go: switch to that pattern and press “Suggest free slots” right away — otherwise the note would be an announcement without an action. In the pattern list every affected row carries “with suggestions: fits” behind its reason. It is computed once when the device is selected and after that only on a row change — suggesting reads names, value lists, units and types, no measured values.
573
+ * In the **template sheet** and in the „save as template" dialog the device type showed its **pattern key** instead of its name: a blind read „blinds", a media player „mediaPlayer", a slider „levelSlider" — words that appear nowhere else in the interface. The stored value was right all along (the type, since 11.09.2026); only the label came from the wrong field, and since the Admin does not know those three keys, the translated name fell away with them. It now says the same as the pattern picker next to it: „Jalousien · blind".
574
+ * A **button** can now be created. `STOP` on a blind only writes — a key press has no state to read — and that used to defeat the **whole** alias: the dry run said “STOP: no source chosen”, “create now” stayed greyed out, and not a single blind alias ever got a STOP. The first diagnosis was wrong, and that deserves saying: it was not the write check being too strict. **ioBroker rejects an alias without a read source** — `{ id: { write: … } }` yields “Alias id is invalid: The id is empty!”. The correct shape is the one Ricardo has long been using on his ten hand-built STOPs: `alias.id` as plain text pointing at the command point, plus `common.read: false` on the alias point. That tells vis, Alexa and matter to draw a **button** rather than a toggle stuck at “off” forever. What decides is the **target**, not the role: `read: false` is written by the adapter itself (measured on a real `hm-rpc` STOP). If the target is readable the block stays — then the missing read source really is a defect. The button also survives being read back: all three places that reconstruct an alias now know it, otherwise `read` would quietly flip to `true` on the next update. **The same question grew into a new check**: “target can do what is asked”. The workbench writes to foreign points and reads from foreign points without ever checking whether they can do that. Three ways it silently falls apart — the write formula produces text and the target takes none; the target carries `write: false` (ioBroker stores the value anyway but the adapter passes nothing on: a silent dud); the read source carries `read: false` and never reports anything. All three now appear in the checks panel and as a chip on the ID in the detail view, right where the formula is typed
575
+ * The workbench creates MQTT **send points** itself — a `cmnd` point only comes into existence in ioBroker once something has been published to its topic; before that the alias would have nothing to write to. It took the type from the command knowledge, and `POWER` is listed there as `boolean` — right next to the write formula `val ? "ON" : "OFF"`. The two do not add up: the js-controller converts the formula result back to the type of the **target** on write, the `"ON"` becomes `true` again, and the formula sits in the alias without effect. Switching still works — Tasmota takes `on` as well as `true` — but the point looked different from every point mqtt-client creates, and the formula claimed something it did not do. On the production system it was **one of 196** alias points with an MQTT write target, and it came from us. The rule now is: **where a write formula exists, the point is created as `mixed`** — no exception for `POWER` but a rule that also covers `Fade` and `LedTable`. Without a formula the exact type stays, because the admin derives min, max and unit from `number`. **Plus two places where it becomes visible**: the target point's type now shows as a chip behind its ID in the detail view — right where the formula is typed — in warning colour when the two do not match. And the checks panel has an eighth check, "write formula takes effect", that names it instead of keeping quiet
576
+ * A template now counts as fitting only where its **mandatory rows** actually find their source. Until now detection alone decided, and detection asks a different question than application: `pruefeVorlage` checks whether the named **points** exist, the rows need particular **fields inside them**. A hand-built power-meter template whose mandatory row reads `tele.SENSOR · SML.Total_Summe` thus won on **17** ordinary Tasmota devices and delivered exactly one — and unchecked — row there, where the socket template would have built eleven. The device said "identified as: Tasmota power meter", and the draft was empty. Now it does not even enter the race there and the next best one takes the place; what was skipped is stated in the "why". Optional rows do not count, that is what optional means — measured against the stock, **none** of the fifteen other templates loses any of its 51 wins. **And the try-out in the template sheet**: it now also fetches the values of the points the rows read. Without them it judged fields it could not see, and the same template reported different numbers depending on what had been clicked before. It now shows per device how many rows would really come out, and on "does not fit" **which mandatory row is missing** — turning "22 devices matched, 17 of them won" into the honest "22 matched, 1 won · 21× does not fit". **And the try-out now computes like the creation does**: at first it compared only against the single highest-scoring other template — if that one did not fit the device, the template under test declared itself the winner. "Plain measuring point" thus reported a win on a Tasmota although the socket template sits in between and really fits there. It now walks the match list like `vorschlag` does; measured afterwards, all 150 pairs of template and device agree
577
+ * An alias whose source has vanished from the system is now visible without clicking it. Until now it was only said in the expanded detail and in the checks panel — the row in the list stayed quiet because it asked whether a source was **entered**, not whether it **exists**. The row now carries the red `!` with the note "source does not exist", the header a chip "1 point points nowhere" with the list in its tooltip, and the alias tree a red `!` on the affected channel. This is not cosmetic: the js-controller remembers "source missing" permanently and never corrects it — only deleting and recreating helps. It is computed once when the objects load, not on every redraw; on the production system 531 alias points in 5.2 ms. Two were found straight away: a window contact still pointing at the long-replaced `zigbee` adapter, and an NSPanel point with a missing `0_userdata` source for both reading and writing. A row with **no** source at all keeps its own note — it is unfinished, not broken
578
+ * New: **"Referenced in"** on an existing alias. The box below the change comparison searches on click for whatever else mentions the ID — the **scripts** of the javascript adapter with folder, name, state and line numbers, the **files** of the instance folders (that is, the vis and vis-2 views), every loaded **object** (where the matter bridges keep their members, for instance), the **enumerations** and the entries on the point itself (`SET → influxdb.0`) — and the same again for the **source**, including further aliases on it. That answers the question of what was left behind when moving to the alias. Measured on the production system with 24,706 objects and 66 files: the first look takes 1.3 seconds, every further alias after that 0.17. The web interfaces of the adapters are not searched — 160 of the 177 MB of text in the file tree, identical on every ioBroker, and they carry examples like `alias.0.Floor.Room.MotionSensor`; without that boundary the workbench would find its own source files as users of itself. Deliberately without a verdict: a source that is still mentioned is not a fault — a script that feeds it belongs exactly there (of 376 aliases with a source, 61 still had the source mentioned directly, a good share of them rightly so). **And the delete dialog now searches the scripts too** — that only existed when moving, although deleting is the irreversible one
579
+ * The field picker on a JSON point now copes with every key. Until now the workbench glued the field name onto the access with dots — which only works as long as every part is a valid JavaScript name. `gpu_usages.amd-vaapi.gpu` turned into an arithmetic expression (`?.amd - vaapi?.gpu`), `service.storage./dev/shm.free` and `cpu_usages.1111740.mem` into a syntax error. Measured on a Frigate telegram: of 233 offered fields **27** were usable, the other 206 ended in the red "formula broken" message. `feldFormel` now uses brackets where the key is not a name — `JSON.parse(val)?.gpu_usages?.['amd-vaapi']?.gpu ?? null` — and reads them back again, so the picker stays on the chosen field instead of falling back to "own formula". For ordinary names **not a single character** changes, so the shipped templates are untouched. A dot inside the key itself (`frigate.full_system`) remains unresolvable and still shows "own formula"
580
+
581
+ ### 0.9.10 (2026-09-13)
582
+ * Four things from the full test run. **A channel identifier** was not sanitised in the multi-output path: from the channel name “Licht, Bar” the js-controller quietly made `Licht_ Bar`, while the workbench displayed “Licht, Bar” and wrote it into the enumerations — an identifier that never existed, and after deleting the alias it stayed there as a leftover. `ausgangName` now sanitises itself, and where two channels produce the same name the second gets a `_2`. **The caption** differed between the two views: where the point's name equals the row name, “Edit alias” showed it and “Create alias” an empty field. **The row list** now counts like the tree — `POWER2` before `POWER10`. And **an enumeration the workbench created itself** now also disappears when it becomes empty through a change of function, not only when the alias is deleted
583
+ * Tidying: "turn an alias object into a draft row" was written out five times — each with the same write-source rule and slightly different surroundings. Four bugs in this version came from exactly that: the lost write source on the roller shutter, a switching point that became a read-only one, the missing caption on `alias.0.Solar.Netz`, and a deviation the workbench had produced itself. The reading now lives in one place; what the four callers do with it — compare, create, overwrite — stays with them. Behaviour is unchanged
584
+ * Three small things spotted during the run. **RSSI** was pre-ticked in the five Tasmota and measuring-point templates but not in the eleven Homematic ones — the same device property, two answers depending on the vendor; it is now unticked everywhere, like the rest of the diagnostics. **The `MAP_URL` slot** in the vacuum pattern got no role because its expression starts without `^` and the derivation demanded both anchors; a missing start anchor no longer counts against a slot. **And the "guessed" tag** in the swap dialog looked like text stuck to the point name — its styling hung on three parent levels that do not exist there. It is now a pill like in the state list and says in its tooltip whether the guess came from the point name or from the role
585
+ * The save button in the template dialog now also checks when pressed, not only when drawn. It is re-evaluated when a field is left — typing an open bracket into the name hint and clicking "Save template" without leaving the field left you with a blue button and saved a template with a broken expression. The five lock reasons now live in one place that drawing and saving share
586
+ * Three places now say why they cannot. **The save button in the template dialog** was greyed out and silent — five reasons can lock it and none of them was shown; every applicable one now appears on the button and as a box in the dialog. **The error dialog when deleting** only gave a count ("1 operations failed") and not which id is stuck; writing has had that list all along. **And the reason a send point could not be created** hung on a button that the same callback threw away 120 ms later — it now sits at the top of the dry run and survives the rebuild
587
+ * If a point's type does not match its pattern slot, all three places now say so — not just the expanded row. If the slot was **required**, the pattern stops applying altogether, and the whole list showed up tinted: on the Bastelzimmer_Licht, switching SET from `boolean` to `mixed` was enough for eight of eight rows to count as "no slot", while the pattern field reported "✕ SET missing" — SET was right there. It now reads "✕ SET: type mixed instead of boolean", and above the list: "The light pattern does not apply right now: SET requires boolean, but mixed is set. That is why no row has a slot." Only the row that causes it is tinted now — the whole list used to light up, which showed the consequence instead of the cause.
588
+ * A late answer to `system.config` now really does carry the expert mode over. The tab gives up after 2.5 seconds and continues with the browser language; if the answer arrives later, the language stays as it is (switching after the labels are drawn would be worse), the expert mode is adopted and the tree is redrawn once. That had long been built — but above it sat a guard from the time before, which bailed out in exactly this case. Without the mode, every node under `system.*` and `enum.*` was missing, with nothing saying why
589
+ * The swap dialog notices when one level up fits better. The picker lets you choose any node with data points underneath — it has to, because on a flat adapter the channel is the device. With MQTT, though, `stat`, `tele` and `cmnd` are topic folders: landing there finds fewer points, and the lock would ask you to remove some. The workbench now also computes the parent level and says so — "One level up (…) finds a target for 7 of 7 points, here only 5" — with a button that switches the selection. Nothing is forbidden
590
+ * The device list in the swap dialog keeps its position. Scrolling down a long list and picking a device used to jump back to the top, with the freshly picked entry out of sight — picking rebuilds the dialog, and the tree is a different element afterwards. If the selected row still ends up outside, it is brought into view. A new search and a freshly opened dialog still start at the top
591
+ * When swapping the source, a write target no longer counts if it coincides with the read source. The search tries the bare point name as its last form, so `cmnd.POWER` found a status point that also ends in `POWER` — the alias would then have written to its own read source. Guessing already caught this, matching by path did not; it showed when the new source was picked as a sub-node instead of the device
592
+ * When swapping the source, a point that cannot come along now stops the swap instead of letting it fail halfway. A point with separate sources — reads from `stat.POWER`, writes to `cmnd.POWER` — needs both at the new source. If the write target is missing, ioBroker has **no** form that means "reads, writes nowhere": a plain id means "write to the status point", `{read: X}` without `write` is rejected by the js-controller, and `common.write: false` does not protect the source (measured: writing to such an alias overwrote the read source). So the user decides — the checkbox "remove this point when swapping" now appears in this case as well, and "Swap now" stays disabled until someone has decided; the reason shows on the button and as a line in the dialog. Before, the swap started, failed at the controller and left the alias half on the old and half on the new source
593
+ * If a write fails during the swap, **nothing** is deleted. The deletions used to run anyway, so an alias that was already half migrated lost points on top of it. Moving an alias has followed that rule for a long time
594
+ * "All data points of the device" no longer moves the storage location. For a device with several outputs the workbench creates a folder with one channel per output. The button rebuilds the draft and passed on only the **finished target**; `setzeZiel` reads its last segment as the device name — with several outputs that segment is the output, and it ended up in the folder as well. Every press pushed the location one level deeper, and that is where it was written, too: three clicks produced four folders instead of one (`…Garten-Ventilinsel.POWER2.POWER2.POWER2.POWER2`). The button now passes folder and name separately, the way the output selector next to it has always done. A single-output device was never affected
595
+
567
596
  ### 0.9.9
568
597
  * The build source moved from `src/` to `src-admin/`. ioBroker reserves that name for frontend sources that are bundled into `admin/` and never reach the user, and the adapter checker skips such directories when it looks for missing dependencies. `@iobroker/type-detector` therefore stays in `devDependencies`, which is where it belongs: esbuild bakes it into `admin/detector.js` at build time and nothing loads it at runtime. Users receive the finished 71 KB file, not the 548 KB package (W5042)
569
598
  * The settings page now takes its translations from `admin/i18n/` — the dictionary ioBroker reads itself (`"i18n": true`) and that Weblate can work with. The eleven languages it already carried moved there unchanged; not a word was retranslated. The tab keeps its own dictionary, now at `admin/sprachen/`: 588 keys in German and English, with an unknown language falling back to English. Two dictionaries, two jobs — the name `i18n` is the one the adapter checker inspects, and it expects all eleven languages there (W5603, W5614, W5015)
@@ -377,6 +377,50 @@
377
377
  .aside.w { border-left-color:var(--warn); }
378
378
  .aside b { color:var(--ink); }
379
379
 
380
+ /* Der Kasten „Wird genannt in“.
381
+
382
+ Zuerst mit den Klassen der Zustandsliste gebaut (.slot, .sn, .typ) -
383
+ die sind durchgehend Schreibmaschinenschrift, und der Kasten sah aus
384
+ wie Quelltext statt wie Text. Beschriftung links deshalb wie im
385
+ Detail (.k), Inhalt als gewoehnlicher Satz, und nur Kennungen in
386
+ Schreibmaschine. */
387
+ /* Der Blockkopf muss sich von den Zeilenbeschriftungen darunter
388
+ abheben - stand er in derselben grauen Versalschrift, las sich
389
+ „DER ALIAS“ wie eine weitere Zeile statt wie die Ueberschrift
390
+ darueber (Ricardo, 14.09.2026). Deshalb eigene Leiste mit Grund,
391
+ Akzentfarbe und etwas groesser. */
392
+ .vwkopf {
393
+ display:flex; align-items:baseline; gap:10px; flex-wrap:wrap;
394
+ padding:7px 13px; background:var(--surface-2);
395
+ border-top:1px solid var(--line); border-bottom:1px solid var(--line);
396
+ }
397
+ .vwkopf .t {
398
+ font-family:var(--sans); font-size:10px; text-transform:uppercase;
399
+ letter-spacing:.08em; color:var(--accent); font-weight:700;
400
+ }
401
+ .vwkopf .id { font-family:var(--mono); font-size:12px; color:var(--ink); word-break:break-all; }
402
+ .vwzeile {
403
+ display:grid; grid-template-columns:148px minmax(0,1fr); gap:10px;
404
+ align-items:start; padding:4px 13px; font-size:12px; color:var(--ink);
405
+ }
406
+ .vwzeile .k {
407
+ font-family:var(--sans); font-size:9px; text-transform:uppercase;
408
+ letter-spacing:.06em; color:var(--ink-3); font-weight:700; padding-top:3px;
409
+ }
410
+ .vwzeile .leer { color:var(--ink-3); }
411
+ .vwzeile .id { font-family:var(--mono); font-size:11.5px; word-break:break-all; }
412
+ .vwzeile ul { margin:0; padding-left:15px; }
413
+ .vwzeile li { margin:1px 0; line-height:1.5; }
414
+ .vwzeile li .id { font-weight:600; }
415
+ .vwzeile li .ord { color:var(--ink-3); }
416
+ .vwnote { padding:10px 13px 0; }
417
+ .vwnote:last-child { padding-bottom:12px; }
418
+
419
+ /* Der Alias im Baum, dessen Quelle es nicht mehr gibt. Klein und rot,
420
+ neben Geraetepunkt und Zahl - kein Kasten, keine Toenung: die Zeile
421
+ soll lesbar bleiben. */
422
+ .kaputt { color:var(--bad); font-weight:700; }
423
+
380
424
  .empty { color:var(--ink-3); font-size:13px; padding:36px 8px; text-align:center; }
381
425
  /* Der Erklaertext der leeren Seite: lesbar breit, nicht ueber die
382
426
  ganze Flaeche gezogen, zweiter Absatz gedaempft. */
@@ -460,6 +504,29 @@
460
504
  padding:2px 24px 2px 6px; width:290px; max-width:290px;
461
505
  }
462
506
  .vmeta { display:flex; gap:5px; align-items:center; flex-wrap:wrap; }
507
+ /* „mit Raten: Klimaanlage 9/29, Thermostat 8/24" - der Hinweis neben
508
+ dem Musterfeld. Kein Chip: es ist keine Marke am Geraet, sondern ein
509
+ Weg, den man gehen kann. Deshalb Text in der Farbe der Vermutung,
510
+ nicht in der von Gut oder Schlecht. */
511
+ /* Die Breite ist auf die des Feldes darueber gedeckelt, und der Satz
512
+ darf umbrechen.
513
+
514
+ Mit `nowrap` war er eine 315 px breite Zeile, und weil der Block
515
+ seine Breite aus dem breitesten Kind nimmt, wurde der ganze
516
+ Musterblock breiter. Im Vorlagenkasten passte die Knopfleiste damit
517
+ nicht mehr in dieselbe Zeile, rutschte in die naechste - und deren
518
+ `margin-top:16px` plus der Zeilenabstand von 18 px standen dann als
519
+ 34 px Leere zwischen Hinweis und Knopf (Ricardo, 15.09.2026). Es
520
+ fehlten drei Pixel; jede Wortaenderung haette es wieder kippen
521
+ koennen. Ein Deckel ist verlaesslicher als ein knapper Umbruch. */
522
+ .ratAussicht { font-size:11px; color:var(--accent); max-width:290px;
523
+ display:flex; gap:5px; align-items:center; flex-wrap:wrap; }
524
+ /* Der Knopf traegt die Farbe des Hinweises, nicht die eines
525
+ gewoehnlichen Knopfes - er gehoert zum Satz daneben. Reicht die
526
+ Breite nicht, rutscht er unter den Text statt den Block zu
527
+ verbreitern (siehe den Deckel darueber). */
528
+ .rtgo { color:var(--accent); border-color:var(--accent); flex:none;
529
+ font-size:12px; line-height:1; padding:1px 6px; }
463
530
 
464
531
  /* ---- Geraetekarte ---- */
465
532
 
@@ -553,9 +620,14 @@
553
620
  .erow .nm3 .geae { font-family:var(--sans); font-size:8px; font-weight:700; letter-spacing:.05em;
554
621
  text-transform:uppercase; background:var(--warn-soft); color:var(--warn);
555
622
  padding:0 6px; border-radius:999px; margin-left:6px; }
556
- .erow .nm3 .rat { font-family:var(--sans); font-size:8px; font-weight:700; letter-spacing:.05em;
557
- text-transform:uppercase; background:var(--accent-soft); color:var(--accent);
558
- padding:0 6px; border-radius:999px; margin-left:6px; }
623
+ /* Die Marke gilt ueberall gleich, nicht nur in der Zustandsliste.
624
+ Mit den drei Ebenen davor griff sie im Tausch-Dialog nicht: dort
625
+ haengt das span unter .sn, und "vermutet" stand als blosser Text
626
+ am Punktnamen - ohne Abstand, ohne Farbe, in 12 statt 8 px
627
+ (Ricardo, 12.09.2026, an alias.0.Garten.FK_Badezimmer). */
628
+ .rat { font-family:var(--sans); font-size:8px; font-weight:700; letter-spacing:.05em;
629
+ text-transform:uppercase; background:var(--accent-soft); color:var(--accent);
630
+ padding:0 6px; border-radius:999px; margin-left:6px; white-space:nowrap; }
559
631
  .erow .nm3 .ratknopf { font-family:var(--sans); font-size:11px; cursor:pointer; margin-left:5px;
560
632
  color:var(--ink-3); padding:0 4px; border-radius:var(--r-sm); }
561
633
  .erow .nm3 .ratknopf:hover { background:var(--surface-3); color:var(--ink); }
@@ -710,6 +782,14 @@
710
782
  .tree .geraetepunkt { margin-left:auto; font-size:8px; color:var(--accent); }
711
783
  .tree .node.sel .geraetepunkt { color:var(--accent-ink); }
712
784
  .tree .geraetepunkt + .cnt { margin-left:5px; }
785
+ /* Die Warnung steht in derselben rechten Gruppe wie Geraetepunkt und
786
+ Zahl - und VOR dem Punkt, damit Punkt und Zahl ihr Paar behalten.
787
+ Stand sie dazwischen, verlor `.geraetepunkt + .cnt` seinen Griff und
788
+ die Zahl rutschte allein an den rechten Rand (Ricardo, 14.09.2026). */
789
+ .tree .kaputt { margin-left:auto; font-size:10px; line-height:1; }
790
+ .tree .kaputt + .geraetepunkt { margin-left:6px; }
791
+ .tree .kaputt + .cnt { margin-left:6px; }
792
+ .tree .node.sel .kaputt { color:var(--accent-ink); }
713
793
 
714
794
  .erow .fehltnoch {
715
795
  margin-left:7px; font-style:normal; font-size:10px; font-weight:700;
@@ -809,6 +889,7 @@
809
889
  /* Gleiche Strichstaerke wie Vorlage und Ausgang daneben - halbfett
810
890
  wirkte wie eine andere Schrift in der Zeile. */
811
891
  .suggestbox .verdictwrap .musterwahl { font-weight:400; width:250px; max-width:250px; }
892
+ .suggestbox .ratAussicht { max-width:250px; }
812
893
  .suggestbox .vmeta { font-size:10.5px; color:var(--ink-3); gap:6px; }
813
894
  .suggestbox .luecke { flex:1 1 20px; }
814
895
  .suggestbox .knoepfe { display:flex; gap:7px; align-items:flex-end; }
@@ -931,6 +1012,10 @@
931
1012
  }
932
1013
  .listbar .verdictwrap.inbar .musterwahl { font-size:12px; width:230px; max-width:230px; }
933
1014
  .listbar .verdictwrap.inbar .vmeta { margin:0; }
1015
+ /* In der Leiste steht der Hinweis NEBEN dem Feld (Reihe statt Spalte)
1016
+ und hat die ganze Breite der Leiste zur Verfuegung - dort ist eine
1017
+ Zeile richtig; reicht sie nicht, bricht die Leiste selbst um. */
1018
+ .listbar .verdictwrap.inbar .ratAussicht { max-width:none; white-space:nowrap; flex-wrap:nowrap; }
934
1019
 
935
1020
  .feldwrap { position:relative; display:block; min-width:0; }
936
1021
  /* Das Musterfeld ist ein Eingabefeld, sieht aber aus wie das Auswahlfeld
package/admin/js/baum.js CHANGED
@@ -412,6 +412,14 @@ function renderKinder(knoten, filter, tiefe) {
412
412
  ueberschreibt ihn die Zeile darueber wieder. */
413
413
  if (leer) { d.title = (d.title ? d.title + ' · ' : '') + tr('tree.emptyNode'); }
414
414
  if (waehlbar) {
415
+ /* Ein Alias, dessen Quelle es nicht mehr gibt, faellt sonst nur
416
+ auf, wenn man ihn anklickt und die Zeile aufklappt. Die Menge
417
+ steht fertig in S.aliasKaputt - hier wird nur nachgeschlagen. */
418
+ if (S.aliasKaputt[k.id]) {
419
+ var kp = el('span', 'kaputt', '!');
420
+ kp.title = tr('tree.sourceGone', S.aliasKaputt[k.id]);
421
+ d.appendChild(kp);
422
+ }
415
423
  if (wohlGeraet(k)) {
416
424
  var pk = el('span', 'geraetepunkt', '●');
417
425
  pk.title = tr('tree.looksLikeDevice');
@@ -4,7 +4,7 @@ import { S } from './zustand.js';
4
4
  import { socket } from './verbindung.js';
5
5
  import { el } from './basis.js';
6
6
  import { tr } from './sprache.js';
7
- import { wertVon, jsonFelder, feldAusFormel, feldFormel, feldWert, jsonVon } from './werte.js';
7
+ import { wertVon, jsonFelder, feldAusFormel, feldFormel, feldWert, jsonVon, zielKann } from './werte.js';
8
8
  import { musterVon, erkenneEntwurf, platzFuerRolle, typenFuerRolle } from './erkennung.js';
9
9
  import { musterName } from './musternamen.js';
10
10
  import { rollenFeld } from './rollenwahl.js';
@@ -71,15 +71,59 @@ export function detailZeile(e, s, idx) {
71
71
  steht die volle Kennung als eigene Zeile unter dem Feld. Dieselbe
72
72
  Bauart wie „Die Vorlage will hier: …": sie darf umbrechen und hat
73
73
  damit keine Breitengrenze. */
74
- function mitVollId(felder, id) {
74
+ function mitVollId(felder, id, zusatz) {
75
75
  var w = el('div', 'feldstapel');
76
76
  w.appendChild(felder);
77
77
  var v = el('div', 'vollid');
78
78
  if (id) { v.textContent = id; v.title = id; }
79
+ /* Beim Schreibziel steht hinter der Kennung sein Typ. Er entscheidet,
80
+ ob die Schreibformel ueberhaupt ankommt - der js-controller rechnet
81
+ den Formelwert auf den Typ des ZIELS zurueck. Man tippt die Formel
82
+ also an einer Stelle, an der man bisher nicht sehen konnte, ob sie
83
+ etwas bewirkt (Ricardo, 15.09.2026). */
84
+ if (zusatz) { v.appendChild(zusatz); }
79
85
  w.appendChild(v);
80
86
  return w;
81
87
  }
82
88
 
89
+ /* Was der fremde Punkt kann, als kleine Marke hinter seiner Kennung.
90
+
91
+ Beide Seiten zeigen dasselbe, nur aus verschiedener Richtung: am
92
+ Schreibziel seinen Typ (er entscheidet, was von der Formel ankommt)
93
+ und ob es sich ueberhaupt beschreiben laesst; an der Lesequelle, ob
94
+ sie etwas meldet. `zielKann` in werte.js ist die eine Stelle, die
95
+ darueber urteilt - die Pruefungen-Karte fragt dieselbe Funktion. */
96
+ function zielMarke(s, seite) {
97
+ var id = seite === 'w' ? s.srcW : s.srcR;
98
+ if (!id) { return null; }
99
+ var o = S.objects[id];
100
+ if (!o || !o.common) { return null; }
101
+ var c = o.common;
102
+
103
+ var maengel = zielKann(s).filter(function (x) {
104
+ return seite === 'w' ? x.art !== 'meldetNichts' : x.art === 'meldetNichts';
105
+ });
106
+
107
+ var text, hinweis;
108
+ if (seite === 'w') {
109
+ if (!c.type) { return null; }
110
+ text = c.type;
111
+ var nw = maengel.some(function (x) { return x.art === 'nichtSchreibbar'; });
112
+ var bl = maengel.some(function (x) { return x.art === 'formelBlind'; });
113
+ hinweis = nw ? tr('detail.targetNoWrite')
114
+ : (bl ? tr('detail.targetTypeVoid', c.type) : tr('detail.targetTypeHint', c.type));
115
+ if (nw) { text = c.type + ' \u00b7 ' + tr('detail.notWritable'); }
116
+ } else {
117
+ if (!maengel.length) { return null; }
118
+ text = tr('detail.reportsNothing');
119
+ hinweis = tr('detail.targetNoRead');
120
+ }
121
+
122
+ var m = el('span', 'chip ' + (maengel.length ? 'warn' : 'mut'), text);
123
+ m.style.marginLeft = '7px';
124
+ m.title = hinweis;
125
+ return m;
126
+ }
83
127
  /* ---- Wo die Vorlage etwas anderes will --------------------------
84
128
 
85
129
  Die zugeklappte Zeile sagt nur, DASS etwas abweicht. Welches Feld,
@@ -282,7 +326,7 @@ export function detailZeile(e, s, idx) {
282
326
  lF.appendChild(selF);
283
327
  qf.appendChild(lF);
284
328
  }
285
- zeigeBeide(zeile(tr('detail.readsFrom'), mitVollId(qf, s.srcR)), ['srcR']);
329
+ zeigeBeide(zeile(tr('detail.readsFrom'), mitVollId(qf, s.srcR, zielMarke(s, 'r'))), ['srcR']);
286
330
 
287
331
  if (s.frei) {
288
332
  var iFrei = el('input', 'tx w');
@@ -322,7 +366,7 @@ export function detailZeile(e, s, idx) {
322
366
  if (s.srcW && s.srcW !== s.srcR) {
323
367
  wf.appendChild(el('div', 'sugg', tr('detail.separateHint')));
324
368
  }
325
- zeigeBeide(zeile(tr('detail.writesTo'), mitVollId(wf, s.srcW)), ['srcW']);
369
+ zeigeBeide(zeile(tr('detail.writesTo'), mitVollId(wf, s.srcW, zielMarke(s, 'w'))), ['srcW']);
326
370
 
327
371
  /* --- Leseformel --- */
328
372
  var fb = el('div');
@@ -436,7 +480,10 @@ export function detailZeile(e, s, idx) {
436
480
  iU.value = s.unit || '';
437
481
  iU.placeholder = '—';
438
482
  iU.addEventListener('click', function (ev) { ev.stopPropagation(); });
439
- iU.addEventListener('input', function () { s.unit = iU.value; s.geaendert = true; });
483
+ /* `rateBehalten` gehoert zu jedem `geaendert` dazu - sonst traegt die
484
+ Zeile beim naechsten Zeichnen beide Marken („geaendert" UND
485
+ „vermutet"), und die schliessen sich aus (Ricardo, 15.09.2026). */
486
+ iU.addEventListener('input', function () { s.unit = iU.value; s.geaendert = true; rateBehalten(s); });
440
487
  iU.addEventListener('change', function () { neu(); });
441
488
  var lU = el('label', 'fld');
442
489
  lU.appendChild(el('span', null, tr('detail.unit')));
@@ -461,7 +508,7 @@ export function detailZeile(e, s, idx) {
461
508
  iC.value = s.caption || '';
462
509
  iC.placeholder = s.n;
463
510
  iC.addEventListener('click', function (ev) { ev.stopPropagation(); });
464
- iC.addEventListener('input', function () { s.caption = iC.value; s.geaendert = true; });
511
+ iC.addEventListener('input', function () { s.caption = iC.value; s.geaendert = true; rateBehalten(s); });
465
512
  /* Was das Feld tut, stand nirgends - man tippte und sah keine
466
513
  Wirkung. Der Wert wird der Anzeigename des Punkts (common.name)
467
514
  und erscheint als Zusatz in der Zeile darueber. */
@@ -9,7 +9,7 @@ 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,
12
- mitVorspann, schreibQuelle
12
+ mitVorspann, schreibQuelle, zeileAusAlias
13
13
  } from './werte.js';
14
14
  import { erkenneEntwurf } from './erkennung.js';
15
15
  import { vorschlag , pruefeVorlage, zeigeAbozahl } from './vorlagen.js';
@@ -58,6 +58,17 @@ export function merkeHakenVorgabe(e) {
58
58
  return e;
59
59
  }
60
60
 
61
+ /* Was von `common.name` als Beschriftung uebrig bleibt.
62
+
63
+ Gleicht der Name dem Zeilennamen, ist er keine eigene Beschriftung,
64
+ sondern die Vorgabe - dann bleibt das Feld leer. Dieselbe Regel gilt
65
+ in `zeileAusAlias` (werte.js) und damit an allen vier Stellen, die
66
+ eine Zeile aus einem Alias-Objekt bauen. */
67
+ function beschriftungAus(name, zeilenname) {
68
+ var nm = txt(name);
69
+ return (nm && nm !== zeilenname) ? nm : '';
70
+ }
71
+
61
72
  export function baueEntwurf(kanal) {
62
73
  var istAlias = (kanal.indexOf('alias.') === 0);
63
74
  var e = { kanal: kanal, states: [], want: null, wantAuto: null, alleMuster: false };
@@ -91,8 +102,14 @@ export function baueEntwurf(kanal) {
91
102
  states: c.states || undefined,
92
103
  wr: !!c.write,
93
104
  /* Ohne common.alias ist der Punkt seine eigene Quelle. Vorher stand
94
- hier nichts und man musste jede Zeile von Hand zuweisen. */
95
- srcR: istAlias ? (q.read || '') : id,
105
+ hier nichts und man musste jede Zeile von Hand zuweisen.
106
+
107
+ Ein Taster hat keine Lesequelle, obwohl `alias.id` eine Kennung
108
+ nennt: ioBroker verlangt sie, `common.read: false` sagt, dass dort
109
+ nichts abzuholen ist (K23). Dieselbe Ablesung wie in
110
+ `zeileAusAlias` — sonst zeigte „Alias bearbeiten" eine Quelle,
111
+ die „Alias anlegen" nie eingetragen hat. */
112
+ srcR: istAlias ? ((c.read === false && q.einfach) ? '' : (q.read || '')) : id,
96
113
  /* Steht in `common.alias.id` ein einzelner Text, gilt er fuer Lesen
97
114
  UND Schreiben - dann ist die Schreibquelle dieselbe wie die
98
115
  Lesequelle. Hier stand frueher eine leere Zeichenkette, gedacht
@@ -106,7 +123,18 @@ export function baueEntwurf(kanal) {
106
123
  srcW: istAlias ? schreibQuelle(o) : (c.write ? id : ''),
107
124
  f: (typeof a.read === 'string') ? a.read : '',
108
125
  fw: (typeof a.write === 'string') ? a.write : '',
109
- caption: istAlias ? txt(c.name) : kanalBeschriftung(kanal, kurzName),
126
+ /* Dieselbe Regel wie ueberall sonst: „leer, wenn sie dem
127
+ Zeilennamen gleicht".
128
+
129
+ Hier stand `istAlias ? txt(c.name) : …` ohne diese Pruefung.
130
+ Folge: Wo `common.name` dem Punktnamen gleicht - bei fast jedem
131
+ Alias, den die Werkbank selbst angelegt hat -, zeigte „Alias
132
+ bearbeiten" den Namen und „Alias anlegen" ein leeres Feld.
133
+ Derselbe Alias, zwei Ansichten, zwei Antworten (U21, gemessen
134
+ 12.09.2026). Geschrieben wurde in beiden Faellen dasselbe; zu
135
+ sehen war es trotzdem. */
136
+ caption: istAlias ? beschriftungAus(c.name, kurzName)
137
+ : kanalBeschriftung(kanal, kurzName),
110
138
  manuell: false
111
139
  });
112
140
  });
@@ -1167,18 +1195,37 @@ export function bestandVorrang(e, aliasId) {
1167
1195
  her, nachdem der Bestand ihn ueberschrieben hat. */
1168
1196
  on: s.on
1169
1197
  };
1198
+ var ist = zeileAusAlias(o, s.n);
1199
+ /* Rolle und Typ NUR, wenn sie im Alias ueberhaupt stehen.
1200
+
1201
+ `zeileAusAlias` liefert an dieser Stelle `''` - fuer die anderen
1202
+ drei Aufrufer richtig, hier nicht: ein Alias ohne eigene Rolle
1203
+ soll die der Vorlage behalten, statt sie gegen einen Leerstring zu
1204
+ tauschen. Das ist der eine Unterschied, der beim Zusammenlegen der
1205
+ fuenf Stellen erhalten bleiben musste (Y5) - wer ihn wegraeumt,
1206
+ nimmt jedem vorlagenbasierten Alias seine Rollen. */
1170
1207
  if (c.role !== undefined) { s.role = c.role; }
1171
1208
  if (c.type !== undefined) { s.typ = c.type; }
1172
- s.unit = (c.unit !== undefined) ? c.unit : '';
1209
+ s.unit = ist.unit;
1173
1210
  s.states = c.states;
1174
1211
  /* Die Beschriftung gehoert dem Nutzer - sie wird auch spaeter vom
1175
- Vorlagen-Abgleich nicht angefasst (Ricardo, 25.08.2026). */
1176
- if (c.name !== undefined) { s.caption = txt(c.name); }
1177
- var a = c.alias || {};
1178
- s.f = (typeof a.read === 'string') ? a.read : '';
1179
- s.fw = (typeof a.write === 'string') ? a.write : '';
1180
- if (q.read) { s.srcR = q.read; }
1181
- s.srcW = schreibQuelle(o);
1212
+ Vorlagen-Abgleich nicht angefasst (Ricardo, 25.08.2026).
1213
+
1214
+ Seit dem 12.09.2026 gilt hier dieselbe Regel wie ueberall sonst:
1215
+ gleicht sie dem Zeilennamen, bleibt das Feld leer. Vorher stand
1216
+ hier `if (c.name !== undefined) s.caption = txt(c.name)` - und
1217
+ weil `vomAliasUebernehmen` danach laeuft und verwirft, hing es
1218
+ an der Reihenfolge, welche der beiden Antworten man zu sehen
1219
+ bekam (U21). */
1220
+ if (c.name !== undefined) { s.caption = ist.caption; }
1221
+ s.f = ist.f;
1222
+ s.fw = ist.fw;
1223
+ /* `ist.srcR`, nicht `q.read`: bei einem Taster laesst
1224
+ `zeileAusAlias` die Lesequelle bewusst leer, obwohl `alias.id` eine
1225
+ Kennung nennt (K23). Die Wache auf `q.read` bleibt — sie verhindert,
1226
+ dass ein Alias ohne Quellangabe die vorhandene ueberschreibt. */
1227
+ if (q.read) { s.srcR = ist.srcR; }
1228
+ s.srcW = ist.srcW;
1182
1229
  s.on = true;
1183
1230
  s.ausBestand = true;
1184
1231
  });
@@ -1209,14 +1256,7 @@ export function bestandsAbweichung(s, aliasId) {
1209
1256
  if (!s || !s.n || !s.on || !aliasId) { return []; }
1210
1257
  var o = S.objects[aliasId + '.' + s.n];
1211
1258
  if (!o || !o.common) { return []; }
1212
- var c = o.common, a = c.alias || {}, q = aliasQuellen(o);
1213
- var ist = {
1214
- role: c.role || '', typ: c.type || '', unit: c.unit || '',
1215
- f: (typeof a.read === 'string') ? a.read : '',
1216
- fw: (typeof a.write === 'string') ? a.write : '',
1217
- srcR: q.read || '',
1218
- srcW: schreibQuelle(o)
1219
- };
1259
+ var ist = zeileAusAlias(o, s.n);
1220
1260
  var raus = [];
1221
1261
  ['role', 'typ', 'unit', 'f', 'fw', 'srcR', 'srcW'].forEach(function (k) {
1222
1262
  var jetzt = s[k] || '';
@@ -38,6 +38,7 @@ import {
38
38
  import { setzeEnums, setzeZiel, unterschiede } from './zuordnung.js';
39
39
  import './detail.js';
40
40
  import { zeigeTausch, tauscheAus } from './quellentausch.js';
41
+ import { baueVerwendungKarte } from './verwendung.js';
41
42
 
42
43
  import { baueChecks } from './pruefungen.js';
43
44
  import {
@@ -141,7 +142,15 @@ var warumAuf = false;
141
142
  }
142
143
 
143
144
  export function entwurfAngefasst() {
144
- if (S.entwurf) { S.entwurf.angefasst = true; }
145
+ if (S.entwurf) {
146
+ S.entwurf.angefasst = true;
147
+ /* Die Aussicht „was passt nach dem Raten" haengt allein an den
148
+ Zeilen. Sie wird einmal gerechnet und gemerkt; eine Zeilen-
149
+ aenderung ist der einzige Anlass, sie fallen zu lassen. Beim
150
+ blossen Neuzeichnen bleibt sie stehen - das Raten liest keine
151
+ Messwerte, es kaeme dasselbe heraus. */
152
+ S.entwurf.rateAussicht = null;
153
+ }
145
154
  }
146
155
 
147
156
  export function neuZeichnenOderAufbauen(danach) {
@@ -323,6 +332,21 @@ var warumAuf = false;
323
332
  will, musste ihn bisher im Quellenbaum von Hand wiederfinden.
324
333
  Umgekehrt gilt dasselbe: steht man am Geraet und will nachsehen,
325
334
  was daraus geworden ist, fuehrt der zweite Knopf hin. */
335
+ /* Zeigt ein Punkt auf eine Quelle, die es nicht gibt, gehoert das
336
+ nach oben - nicht nur in die Zeile und in den Pruefungen-Kasten.
337
+ Der js-controller repariert diesen Zustand nie von allein. */
338
+ var insLeere = (S.entwurf && S.entwurf.states || []).filter(function (s) {
339
+ return s.on && s.srcR && !S.objects[s.srcR];
340
+ });
341
+ if (insLeere.length) {
342
+ sub.appendChild(document.createTextNode(' '));
343
+ var ml = el('span', 'chip bad', insLeere.length === 1
344
+ ? tr('result.sourceGone1')
345
+ : tr('result.sourceGoneN', insLeere.length));
346
+ ml.title = insLeere.map(function (s) { return s.n + ' → ' + s.srcR; }).join('\n');
347
+ sub.appendChild(ml);
348
+ }
349
+
326
350
  var sprung = null, sprungZiel = null, sprungModus = null;
327
351
  if (S.current.indexOf('alias.') === 0) {
328
352
  sprungZiel = quelleVon(S.current);
@@ -705,6 +729,27 @@ var warumAuf = false;
705
729
  neuE.roh = rohJetzt;
706
730
  neuE.vorschlag = true;
707
731
  neuE.tplName = e.tplName; neuE.grund = e.grund; neuE.ziel = e.ziel;
732
+ /* Ordner und Name getrennt mitgeben, nicht nur das fertige Ziel.
733
+
734
+ `setzeZiel` rechnet den Ordner nur aus, solange keiner dasteht —
735
+ und dabei liest es das letzte Segment von `e.ziel` als
736
+ Geraetenamen. Bei einem Geraet mit mehreren Ausgaengen ist das
737
+ letzte Segment aber der AUSGANG (`…Garten-Ventilinsel.POWER2`),
738
+ und der Ordner bekam ihn angehaengt. Jeder Druck auf diesen
739
+ Knopf schob den Ablageort eine Ebene tiefer, und geschrieben
740
+ wurde er auch so: gemessen 12.09.2026 am Testsystem entstanden
741
+ nach drei Klicks vier Ordner statt einem
742
+ (`…Garten-Ventilinsel.POWER2.POWER2.POWER2.POWER2`).
743
+
744
+ Der Ausgangswechsel eine Stelle weiter oben macht es seit jeher
745
+ richtig — hier dieselbe Form. Im Aliasmodus sind beide Felder
746
+ `undefined`; dann bleibt alles wie bisher, `setzeZiel` steigt
747
+ dort ohnehin sofort aus. `ordnerVonHand` gehoert mit, sonst
748
+ verliert der Raumvorschlag die Entscheidung, wer den Ordner
749
+ gesetzt hat (E41). */
750
+ neuE.zielOrdner = e.zielOrdner;
751
+ neuE.zielName = e.zielName;
752
+ neuE.ordnerVonHand = e.ordnerVonHand;
708
753
  if (fest) { neuE.vonHandGewaehlt = false; neuE.ausBestand = true; }
709
754
  if (vorh && S.objects[vorh]) { bestandVorrang(neuE, vorh); }
710
755
  S.entwurf = neuE; S.openRow = null; zeichneErgebnis();
@@ -827,15 +872,32 @@ var warumAuf = false;
827
872
  ba.hidden = !mehr && !mehrK;
828
873
  if (mehr) {
829
874
  ba.textContent = tr('write.createAll', e.instanzen.length);
875
+ /* Dieselbe Rechnung wie beim Schreiben, samt Kollisionsschutz.
876
+
877
+ Zwei Kanaele koennen nach der Saeuberung denselben Namen
878
+ ergeben („Licht Bar" und „Licht, Bar" beide `Licht_Bar`); der
879
+ Schreibpfad haengt dann `_2` an. Ohne dieselbe Rechnung stuende
880
+ im Hinweis zweimal derselbe Name und danach etwas anderes am
881
+ System - genau die Luecke, die G31 beschreibt (12.09.2026). */
882
+ var belegt = {};
830
883
  ba.title = e.instanzen.map(function (n) {
831
- return (e.zielOrdner || '') + '.' + ausgangName(e, n);
884
+ var nm = (String(n) === String(e.instanz)) ? e.zielName : ausgangName(e, n);
885
+ if (belegt[nm]) {
886
+ var z = 1, n2;
887
+ do { z++; n2 = nm + '_' + z; } while (belegt[n2]);
888
+ nm = n2;
889
+ }
890
+ belegt[nm] = 1;
891
+ return (e.zielOrdner || '') + '.' + nm;
832
892
  }).join(String.fromCharCode(10));
833
893
  } else if (mehrK) {
834
894
  ba.textContent = tr('write.createPerChannel', e.kanalGeraete.length);
835
895
  /* Im Hinweis stehen die Namen, die entstehen wuerden — sonst
836
896
  klickt man auf eine Zahl und weiss nicht, was danach dasteht. */
837
897
  ba.title = e.kanalGeraete.map(function (k) {
838
- return (e.zielOrdner || '') + '.' + k.name + ' (' + k.typ + ')';
898
+ /* Die Kennung, nicht den Anzeigenamen - so heisst das Objekt
899
+ hinterher wirklich (G31). */
900
+ return (e.zielOrdner || '') + '.' + (k.kennung || k.name) + ' (' + k.typ + ')';
839
901
  }).join(String.fromCharCode(10));
840
902
  }
841
903
  ba.disabled = false;
@@ -933,6 +995,15 @@ var warumAuf = false;
933
995
  /* --- Änderungen gegenüber dem, was gespeichert ist --- */
934
996
  baueDiffKarte(host, e);
935
997
 
998
+ /* --- Wo wird das benutzt? ---
999
+
1000
+ Nur an einem Alias, den es schon gibt: an einem Entwurf, der noch
1001
+ nicht geschrieben ist, kann ihn niemand nennen. */
1002
+ var vAlias = e.ziel || e.kanal;
1003
+ if (vAlias && String(vAlias).indexOf('alias.') === 0 && S.objects[vAlias]) {
1004
+ baueVerwendungKarte(host, vAlias, quelleVon(vAlias));
1005
+ }
1006
+
936
1007
  /* --- Prüfungen, Fusszeile, Knopfleiste --- */
937
1008
  setzeKnoepfe(e, haupt, pflichtFehlt);
938
1009
  }
@@ -1050,9 +1121,16 @@ var warumAuf = false;
1050
1121
  function verkabeleDialoge() {
1051
1122
  var bc = $('#btn-checks'), bd = $('#btn-dry'), bw = $('#btn-dry-write');
1052
1123
  if (bc) { bc.addEventListener('click', function () { $('#dlg-checks').showModal(); }); }
1053
- if (bd) { bd.addEventListener('click', function () { zeigeTrockenlauf(false); }); }
1124
+ /* Beim Oeffnen von Hand faengt der Dialog frisch an - ein alter
1125
+ Sendepunkt-Fehler von vorhin hat dann nichts mehr zu sagen (H17).
1126
+ Aus dem Rueckruf heraus wird zeigeTrockenlauf direkt gerufen, dort
1127
+ bleibt die Meldung also stehen. */
1128
+ var frisch = function (alle) {
1129
+ return function () { S.mqFehler = null; zeigeTrockenlauf(alle); };
1130
+ };
1131
+ if (bd) { bd.addEventListener('click', frisch(false)); }
1054
1132
  var ba2 = $('#btn-dry-alle');
1055
- if (ba2) { ba2.addEventListener('click', function () { zeigeTrockenlauf(true); }); }
1133
+ if (ba2) { ba2.addEventListener('click', frisch(true)); }
1056
1134
  if (bw) { bw.addEventListener('click', schreibeObjekte); }
1057
1135
  var bdel = $('#btn-del'), bdg = $('#btn-del-go');
1058
1136
  if (bdel) { bdel.addEventListener('click', zeigeLoeschen); }