hmgr 0.0.14 → 0.0.15

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/dist/gui/main.js CHANGED
@@ -24,6 +24,10 @@ import { hmgrCliVersion } from "./hmgr-cli.js";
24
24
  import { cliUpdateHinweis } from "../adapter/npm-registry.js";
25
25
  const hierVerzeichnis = path.dirname(fileURLToPath(import.meta.url));
26
26
  const smoketest = process.env.HMGR_GUI_SMOKETEST === '1';
27
+ // E84 Teil 1: Pfad eines im Fixture noch unbekannten Verzeichnisses, das der Smoke-Lauf per Dialog
28
+ // hinzufügt, um den Sprung zur neuen Karte zu belegen. Nur zur Konstruktionszeit des DOM-Auszug-
29
+ // Skripts gelesen (Node-Seite), nicht zur Laufzeit im Renderer.
30
+ const smoketestNeuesVerzeichnis = process.env.HMGR_SMOKETEST_NEUES_VERZEICHNIS ?? '';
27
31
  /**
28
32
  * Nur eine hmgr-Instanz je Nutzer (E67). Grund: zwei laufende Fenster schrieben nacheinander auf
29
33
  * dieselbe `state.json` und überschrieben sich gegenseitig — der Zustand ist einer, nicht einer je
@@ -945,6 +949,67 @@ function fensterOeffnen() {
945
949
  } catch (fehler) {
946
950
  dmhFehler = String(fehler);
947
951
  }
952
+ // E84 Teil 2: Suchfeld über der Zielliste filtert Agent- und Verzeichnis-Karten. Erst
953
+ // zurück auf die Ziele-Ansicht (der Lauf steht seit der MCP-Prüfung oben auf „mcp").
954
+ document.querySelector('[data-ansicht="ziele"]')?.click();
955
+ var zielsucheVorhanden = document.getElementById('zielsuche') !== null;
956
+ var setzeZielsuche = function (wert) {
957
+ var feld = document.getElementById('zielsuche');
958
+ if (!feld) return;
959
+ feld.value = wert;
960
+ feld.dispatchEvent(new Event('input', { bubbles: true }));
961
+ };
962
+ // „kunde-x" trifft im Fixture nur die Verzeichnis-Karte, kein Agent-Titel heißt so.
963
+ setzeZielsuche('kunde-x');
964
+ var zielzahlNachSuche = document.getElementById('zielzahl')?.textContent ?? null;
965
+ var zielKartenNachSucheZahl = document.querySelectorAll('#zielliste .ziel').length;
966
+ var agentsKopfNachSucheVorhanden = Array.from(document.querySelectorAll('#zielliste .gruppenkopf'))
967
+ .some(function (h) { return h.textContent === 'Agents'; });
968
+ setzeZielsuche('');
969
+ var zielzahlNachLeeren = document.getElementById('zielzahl')?.textContent ?? null;
970
+ var zielKartenNachLeerenZahl = document.querySelectorAll('#zielliste .ziel').length;
971
+ // Nachbesserung E84 Teil 2: die Suche muss auch die gekürzte Schreibweise (pfadKurz)
972
+ // einer Agent-Karte treffen, nicht nur ihren rohen Pfad — genau das steht im
973
+ // Kartenkopf. „~/.hermes" trifft im Fixture „Hermes · Home" und „Hermes · Profil
974
+ // „researcher““ (beide unter ~/.hermes/…), aber weder Claude Code noch Agents noch
975
+ // die Verzeichnis-Karte „kunde-x".
976
+ setzeZielsuche('~/.hermes');
977
+ var zielzahlNachSucheKurz = document.getElementById('zielzahl')?.textContent ?? null;
978
+ var zielKartenNachSucheKurzTitel = Array.from(document.querySelectorAll('#zielliste .ziel h3'))
979
+ .map(function (h) { return h.textContent; }).join('|');
980
+ var kundeXNachSucheKurzVorhanden = zielKartenNachSucheKurzTitel.indexOf('kunde-x') > -1;
981
+ setzeZielsuche('');
982
+ // E84 Teil 1: ein neues Arbeitsverzeichnis über den Dialog anlegen, danach muss seine
983
+ // Karte anspringen (aufgeklappt, kurz hervorgehoben). Der Pfad kommt aus dem Fixture
984
+ // (leer, wenn keiner mitgegeben wurde, dann bleibt dieser Block ein No-Op).
985
+ var sprungNeuesVerzeichnis = ${JSON.stringify(smoketestNeuesVerzeichnis)};
986
+ var sprungFehler = null;
987
+ var sprungKarteVorhanden = false;
988
+ var sprungKarteExpanded = null;
989
+ var sprungKarteHervorgehoben = null;
990
+ try {
991
+ if (sprungNeuesVerzeichnis) {
992
+ document.getElementById('verzneu')?.click();
993
+ var dvPfadFeld = document.getElementById('dv-pfad');
994
+ dvPfadFeld.value = sprungNeuesVerzeichnis;
995
+ document.querySelector('#d-verzeichnis button[value="los"]')?.click();
996
+ // arbeitsverzeichnisHinzufuegen läuft über IPC, dann ladeAlles: kurz nachfassen.
997
+ var sprungKarte = null;
998
+ for (var wartezeitSprung = 0; wartezeitSprung < 3000; wartezeitSprung += 100) {
999
+ await new Promise(function (r) { setTimeout(r, 100); });
1000
+ sprungKarte = Array.from(document.querySelectorAll('#zielliste .ziel.verzeichnis'))
1001
+ .find(function (k) { return (k.querySelector('.zielkopf h3')?.textContent || '').indexOf('neu-verzeichnis') > -1; });
1002
+ if (sprungKarte) break;
1003
+ }
1004
+ if (sprungKarte) {
1005
+ sprungKarteVorhanden = true;
1006
+ sprungKarteExpanded = sprungKarte.querySelector('.zielkopf')?.getAttribute('aria-expanded') ?? null;
1007
+ sprungKarteHervorgehoben = sprungKarte.classList.contains('ziel-neu');
1008
+ }
1009
+ }
1010
+ } catch (fehler) {
1011
+ sprungFehler = String(fehler);
1012
+ }
948
1013
  // Schriften aus fonts/ (Zusatzpunkt 6): erzwingt das Laden und belegt es danach.
949
1014
  await Promise.all([
950
1015
  document.fonts.load('400 16px Inter'),
@@ -1074,6 +1139,19 @@ function fensterOeffnen() {
1074
1139
  verlaufVorhanden,
1075
1140
  verlaufText,
1076
1141
  verlaufBloecke,
1142
+ zielsucheVorhanden,
1143
+ zielzahlNachSuche,
1144
+ zielKartenNachSucheZahl,
1145
+ agentsKopfNachSucheVorhanden,
1146
+ zielzahlNachLeeren,
1147
+ zielKartenNachLeerenZahl,
1148
+ zielzahlNachSucheKurz,
1149
+ zielKartenNachSucheKurzTitel,
1150
+ kundeXNachSucheKurzVorhanden,
1151
+ sprungFehler,
1152
+ sprungKarteVorhanden,
1153
+ sprungKarteExpanded,
1154
+ sprungKarteHervorgehoben,
1077
1155
  });
1078
1156
  })()`)
1079
1157
  .then((auszug) => console.log(`[smoke] DOM-Auszug: ${auszug}`))
@@ -109,6 +109,10 @@
109
109
  </div>
110
110
 
111
111
  <div class="werkzeuge">
112
+ <div class="suche">
113
+ <svg viewBox="0 0 16 16" stroke-width="1.6" aria-hidden="true"><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5 14.5 14.5"/></svg>
114
+ <input type="search" id="zielsuche" placeholder="Ziel oder Pfad suchen" aria-label="Ziele durchsuchen" spellcheck="false" autocomplete="off">
115
+ </div>
112
116
  <div class="filter" role="group" aria-label="Ziele filtern">
113
117
  <button class="chip" data-zielfilter="alle" aria-pressed="true">Alle</button>
114
118
  <button class="chip" data-zielfilter="achtung" aria-pressed="false">Benötigt Aufmerksamkeit (0)</button>
@@ -404,6 +404,20 @@
404
404
  // gilt „alle". Nicht persistiert, nur für die Dauer der Sitzung.
405
405
  var VERZ_CHIP = {};
406
406
 
407
+ // Suchbegriff über die ganze Zielliste (E84 Teil 2, F100): Agent- wie Verzeichnis-Karten, in
408
+ // Kleinschreibung verglichen. Nicht persistiert, nur für die Dauer der Sitzung, und anders als
409
+ // der Chip-Wechsel leert die Suche weder ZIEL_OFFEN noch VERZ_CHIP.
410
+ var zielsuche = '';
411
+
412
+ // Ob schon einmal geladen wurde (E84 Teil 1, F99): der allererste Aufbau kennt noch keinen alten
413
+ // Stand, gegen den sich ein „neu hinzugekommenes" Verzeichnis abgrenzen ließe.
414
+ var ersteLadungAlles = true;
415
+ // Pfad eines Arbeitsverzeichnisses, dessen Karte anspringen soll, sobald die Ziele-Ansicht
416
+ // sichtbar wird (E84 Teil 1): gesetzt, wenn beim Laden eine andere Ansicht offen war. Ein
417
+ // weiteres neues Verzeichnis vor dem Wechsel ersetzt die Vormerkung, zeigeAnsicht('ziele')
418
+ // verbraucht sie.
419
+ var zielSprungVorgemerkt = null;
420
+
407
421
  // Aufgeklappte Skill-Details im Katalog (F10), Schlüssel Quelle + Pfad: der Index in SKILLS
408
422
  // verschiebt sich, wenn ladeAlles Quellen oder Skills anders sortiert oder eine wegfällt.
409
423
  // Nicht persistiert, nur für die Dauer der Sitzung.
@@ -456,6 +470,10 @@
456
470
  rufe(function () { return window.hmgr.bundles(); }),
457
471
  rufe(function () { return window.hmgr.harnessHeime(); }),
458
472
  ]);
473
+ // Alter Stand der Verzeichnis-Pfade, vor dem Überschreiben unten (E84 Teil 1): zielKarten()
474
+ // liest hier noch ZIELE/AKTIVE_HARNESSE von vor diesem Lauf. null heißt „kein Vergleich", der
475
+ // allererste Aufbau hat noch keinen alten Stand.
476
+ var vorherVerzeichnisPfade = ersteLadungAlles ? null : zielKarten().verzeichnisse.map(function (v) { return v.pfad; });
459
477
  ZIELE = ergebnisse[0];
460
478
  SKILLS = ergebnisse[1];
461
479
  // Eine alphabetische Liste über alle Quellen; wer nach Quelle sucht, hat die Filter-Chips.
@@ -476,6 +494,8 @@
476
494
  STANDARD_REF = {};
477
495
  SKILLS.forEach(function (s) { if (STANDARD_REF[s.quelleText] === undefined) STANDARD_REF[s.quelleText] = s.ref; });
478
496
 
497
+ ersteLadungAlles = false;
498
+
479
499
  baueZiele();
480
500
  baueSkills();
481
501
  baueQuellenfilter();
@@ -483,6 +503,18 @@
483
503
  baueEinstellungen();
484
504
  baueMcp();
485
505
  aktualisiereKopf();
506
+
507
+ // Genau ein neuer Verzeichnis-Pfad ⇒ Sprungziel (E84 Teil 1, F99): mehrere neue Pfade oder der
508
+ // allererste Aufbau lösen keinen Sprung aus. Gilt für beide Wege, GUI-Dialog wie CLI-Watcher
509
+ // (E62): beide laufen über ladeAlles. Erst NACH baueZiele() oben: springeZuVerzeichnis baut die
510
+ // Liste selbst noch einmal (Filter/Suche zurücksetzen kann nötig sein) und hebt die Karte
511
+ // hervor — ein baueZiele() danach würde diese frische Karte sonst wieder verwerfen.
512
+ if (vorherVerzeichnisPfade !== null) {
513
+ var neueVerzeichnisPfade = zielKarten().verzeichnisse
514
+ .map(function (v) { return v.pfad; })
515
+ .filter(function (p) { return vorherVerzeichnisPfade.indexOf(p) === -1; });
516
+ if (neueVerzeichnisPfade.length === 1) zielSprungVormerken(neueVerzeichnisPfade[0]);
517
+ }
486
518
  }
487
519
 
488
520
  /** Karten, wie die Zielliste sie zeigt: je Agent-Ziel eine, je Arbeitsverzeichnis eine gebündelte
@@ -545,6 +577,15 @@
545
577
  // Der Verlauf zeigt immer den aktuellen Stand der Datei, nicht nur den des letzten Laufs hier.
546
578
  void ladeVerlauf();
547
579
  }
580
+ // Vorgemerkter Sprung zu einem neu hinzugekommenen Arbeitsverzeichnis (E84 Teil 1): kam er
581
+ // rein, während eine andere Ansicht offen war, holt der Wechsel hierher ihn jetzt nach. Die
582
+ // Ansicht steht schon sichtbar (siehe ANSICHTEN.forEach oben), springeZuVerzeichnis findet die
583
+ // Karte also.
584
+ if (name === 'ziele' && zielSprungVorgemerkt !== null) {
585
+ var vorgemerkterPfad = zielSprungVorgemerkt;
586
+ zielSprungVorgemerkt = null;
587
+ springeZuVerzeichnis(vorgemerkterPfad);
588
+ }
548
589
  window.scrollTo(0, 0);
549
590
  }
550
591
  document.querySelectorAll('[data-ansicht]').forEach(function (k) {
@@ -1258,6 +1299,9 @@
1258
1299
  kopf.setAttribute('role', 'button');
1259
1300
  kopf.setAttribute('tabindex', '0');
1260
1301
  var schluessel = verzKey(pfad);
1302
+ // Wiederfindbar für den Sprung nach dem Hinzufügen (E84 Teil 1): kein CSS-Selektor auf den
1303
+ // Pfad, der könnte Zeichen tragen, die eine Attribut-Selektion nicht unverändert übersteht.
1304
+ karte.dataset.verzKey = schluessel;
1261
1305
  var bodyId = 'zielkoerper-' + zielKoerperNr++;
1262
1306
  var offen = zielFilter === 'achtung' || !!ZIEL_OFFEN[schluessel];
1263
1307
  kopf.setAttribute('aria-controls', bodyId);
@@ -1377,16 +1421,35 @@
1377
1421
  return karte;
1378
1422
  }
1379
1423
 
1424
+ /** Ob eine Agent-Karte zum aktuellen Suchbegriff passt (E84 Teil 2): ihr Titel, ihr voller
1425
+ Verzeichnis-Pfad oder dessen gekürzte Schreibweise (pfadKurz) — genau das, was im Kartenkopf
1426
+ steht (`e('span', 'mono', pfadKurz(z.verzeichnis))` in zielKarte). Ohne die Kürzung fand die
1427
+ Suche nach „~/.claude" nichts, weil im rohen Pfad der volle Home-Pfad steht. In
1428
+ Kleinschreibung, leerer Suchbegriff heißt „alles". */
1429
+ function zielKartePasstZuSuche(z) {
1430
+ var s = zielsuche.trim().toLowerCase();
1431
+ if (!s) return true;
1432
+ var text = (zielTitel(z) + ' ' + (z.verzeichnis || '') + ' ' + pfadKurz(z.verzeichnis || '')).toLowerCase();
1433
+ return text.indexOf(s) > -1;
1434
+ }
1435
+ /** Ob eine Verzeichnis-Karte zum aktuellen Suchbegriff passt (E84 Teil 2): voller Pfad oder
1436
+ gekürzte Schreibweise (pfadKurz). */
1437
+ function verzKartePasstZuSuche(pfad) {
1438
+ var s = zielsuche.trim().toLowerCase();
1439
+ if (!s) return true;
1440
+ return pfad.toLowerCase().indexOf(s) > -1 || pfadKurz(pfad).toLowerCase().indexOf(s) > -1;
1441
+ }
1442
+
1380
1443
  function baueZiele() {
1381
1444
  var liste = $('zielliste');
1382
1445
  liste.textContent = '';
1383
1446
  var karten = zielKarten();
1384
1447
 
1385
1448
  var agentsSichtbar = karten.agents
1386
- .filter(function (z) { return zielFilter === 'alle' || zielBraucht(z); })
1449
+ .filter(function (z) { return (zielFilter === 'alle' || zielBraucht(z)) && zielKartePasstZuSuche(z); })
1387
1450
  .sort(function (a, b) { return zielTitel(a).localeCompare(zielTitel(b), 'de'); });
1388
1451
  var verzeichnisseSichtbar = karten.verzeichnisse
1389
- .filter(function (g) { return zielFilter === 'alle' || g.teile.some(zielBraucht); })
1452
+ .filter(function (g) { return (zielFilter === 'alle' || g.teile.some(zielBraucht)) && verzKartePasstZuSuche(g.pfad); })
1390
1453
  .sort(function (a, b) { return pfadKurz(a.pfad).localeCompare(pfadKurz(b.pfad), 'de'); });
1391
1454
 
1392
1455
  if (agentsSichtbar.length > 0) {
@@ -1399,10 +1462,16 @@
1399
1462
  }
1400
1463
  var gezeigt = agentsSichtbar.length + verzeichnisseSichtbar.length;
1401
1464
  var gesamt = karten.agents.length + karten.verzeichnisse.length;
1402
- if (gezeigt === 0)
1403
- liste.appendChild(e('p', 'leerhinweis', zielFilter === 'achtung'
1404
- ? 'Kein Ziel braucht gerade Aufmerksamkeit.'
1405
- : 'Noch keine Ziele. Füge unten ein Arbeitsverzeichnis hinzu oder verlinke einen Skill.'));
1465
+ var suchbegriffGetrimmt = zielsuche.trim();
1466
+ if (gezeigt === 0) {
1467
+ if (suchbegriffGetrimmt) {
1468
+ liste.appendChild(e('p', 'leerhinweis', 'Kein Ziel passt zu „' + suchbegriffGetrimmt + '".'));
1469
+ } else {
1470
+ liste.appendChild(e('p', 'leerhinweis', zielFilter === 'achtung'
1471
+ ? 'Kein Ziel braucht gerade Aufmerksamkeit.'
1472
+ : 'Noch keine Ziele. Füge unten ein Arbeitsverzeichnis hinzu oder verlinke einen Skill.'));
1473
+ }
1474
+ }
1406
1475
 
1407
1476
  $('zielzahl').textContent = gezeigt + ' von ' + gesamt + ' Karten gezeigt';
1408
1477
 
@@ -1430,6 +1499,73 @@
1430
1499
  });
1431
1500
  });
1432
1501
 
1502
+ // Suchfeld über die ganze Zielliste (E84 Teil 2, F100): tippen filtert live, anders als der
1503
+ // Chip-Wechsel bleiben ZIEL_OFFEN und VERZ_CHIP dabei unangetastet.
1504
+ var zielsucheFeld = $('zielsuche');
1505
+ if (zielsucheFeld) {
1506
+ zielsucheFeld.addEventListener('input', function () {
1507
+ zielsuche = zielsucheFeld.value;
1508
+ baueZiele();
1509
+ });
1510
+ }
1511
+
1512
+ /** Ob die Ziele-Ansicht gerade die sichtbare Ansicht ist (E84 Teil 1). */
1513
+ function ansichtZieleSichtbar() {
1514
+ var ansicht = $('ansicht-ziele');
1515
+ return !!ansicht && !ansicht.hidden;
1516
+ }
1517
+
1518
+ /** Die im DOM stehende Karte eines Arbeitsverzeichnisses, über data-verz-key gefunden (E84
1519
+ Teil 1): kein CSS-Attribut-Selektor auf den Pfad selbst, der könnte Sonderzeichen tragen. */
1520
+ function findeVerzeichnisKarte(schluessel) {
1521
+ var treffer = Array.prototype.filter.call(
1522
+ document.querySelectorAll('#zielliste .ziel.verzeichnis'),
1523
+ function (k) { return k.dataset.verzKey === schluessel; },
1524
+ );
1525
+ return treffer[0];
1526
+ }
1527
+
1528
+ /** Ob die Verzeichnis-Karte unter dem *aktuellen* Filter und Suchbegriff überhaupt sichtbar
1529
+ wäre, ohne beides zurückzusetzen (E84 Teil 1) — dieselbe Regel wie in baueZiele. */
1530
+ function verzKarteWaereSichtbar(pfad) {
1531
+ var gruppe = zielKarten().verzeichnisse.filter(function (g) { return g.pfad === pfad; })[0];
1532
+ if (!gruppe) return false;
1533
+ return (zielFilter === 'alle' || gruppe.teile.some(zielBraucht)) && verzKartePasstZuSuche(pfad);
1534
+ }
1535
+
1536
+ /** Klappt die Karte eines Arbeitsverzeichnisses auf, scrollt sie ins Bild und hebt sie kurz
1537
+ hervor (E84 Teil 1, F99). Setzt Suchfeld und Filter nur zurück, wenn die Karte sonst gar
1538
+ nicht sichtbar wäre — die Reihenfolge ist wichtig: der Filterwechsel leert ZIEL_OFFEN, erst
1539
+ danach wird der Schlüssel der Karte wieder aufgeklappt gemerkt. */
1540
+ function springeZuVerzeichnis(pfad) {
1541
+ var schluessel = verzKey(pfad);
1542
+ if (!verzKarteWaereSichtbar(pfad)) {
1543
+ zielFilter = 'alle';
1544
+ zielsuche = '';
1545
+ if (zielsucheFeld) zielsucheFeld.value = '';
1546
+ document.querySelectorAll('#ansicht-ziele .chip').forEach(function (a) {
1547
+ a.setAttribute('aria-pressed', a.dataset.zielfilter === 'alle' ? 'true' : 'false');
1548
+ });
1549
+ ZIEL_OFFEN = {};
1550
+ VERZ_CHIP = {};
1551
+ }
1552
+ ZIEL_OFFEN[schluessel] = true;
1553
+ baueZiele();
1554
+ var karte = findeVerzeichnisKarte(schluessel);
1555
+ if (!karte) return;
1556
+ karte.scrollIntoView({ block: 'nearest' });
1557
+ karte.classList.add('ziel-neu');
1558
+ setTimeout(function () { karte.classList.remove('ziel-neu'); }, 1500);
1559
+ }
1560
+
1561
+ /** Springt sofort, wenn die Ziele-Ansicht gerade sichtbar ist, sonst merkt sich den Pfad für
1562
+ den nächsten Wechsel dorthin (E84 Teil 1): zeigeAnsicht('ziele') holt die Vormerkung nach
1563
+ und verbraucht sie dabei. Ein späterer Aufruf vor dem Wechsel ersetzt die vorherige. */
1564
+ function zielSprungVormerken(pfad) {
1565
+ if (ansichtZieleSichtbar()) springeZuVerzeichnis(pfad);
1566
+ else zielSprungVorgemerkt = pfad;
1567
+ }
1568
+
1433
1569
  // entfernenKontext.eintraege: [{harness, ort}], immer eines bei frageEntfernen (Einzel-Ziel),
1434
1570
  // mehrere bei frageEntfernenAlle (E53 Pkt. 2, „Alle"-Zeile über mehrere Harnesses).
1435
1571
  var entfernenKontext = null;
@@ -1672,8 +1808,13 @@
1672
1808
  if (wert !== 'los') return;
1673
1809
  var pfad = $('dv-pfad').value.trim();
1674
1810
  if (!pfad) return;
1811
+ // Stand vor dem Anlegen (E84 Teil 1): war das Verzeichnis schon eine Karte, findet ladeAlles
1812
+ // unten keine Differenz (kein neuer Pfad) — der Dialog kennt den Pfad trotzdem, diese Karte
1813
+ // ist dann das Sprungziel.
1814
+ var karteVorhanden = zielKarten().verzeichnisse.some(function (g) { return g.pfad === pfad; });
1675
1815
  var ergebnis = await rufe(function () { return window.hmgr.arbeitsverzeichnisHinzufuegen(pfad); });
1676
1816
  await ladeAlles();
1817
+ if (karteVorhanden) zielSprungVormerken(pfad);
1677
1818
  $('de-titel').textContent = 'Arbeitsverzeichnis hinzugefügt';
1678
1819
  $('de-text').textContent = ergebnis.hinweis || ('Zeiger: ' + (ergebnis.zeigerPfad || 'keiner'));
1679
1820
  setzeErfolgHinweise([]);
@@ -367,6 +367,17 @@ a{color:var(--signal-tief)}
367
367
  .zielkoerper{padding:16px 18px 18px}
368
368
  .zielkoerper[hidden]{display:none}
369
369
 
370
+ /* Hervorhebung der Karte eines frisch hinzugefügten Arbeitsverzeichnisses (E84 Teil 1): kurzer
371
+ Farbschimmer am Kartenrand, die Klasse fällt nach ~1,5s selbst wieder ab (setTimeout in
372
+ renderer.js, damit ein Neuaufbau der Liste sie nicht wieder einschaltet). Die globale
373
+ prefers-reduced-motion-Regel weiter unten kappt die Animationsdauer schon auf .01ms, der
374
+ Ruhezustand (kein Schimmer) reicht dort als Hinweis. */
375
+ .ziel.ziel-neu{animation:ziel-aufblitzen 1.5s ease-out}
376
+ @keyframes ziel-aufblitzen{
377
+ from{box-shadow:0 0 0 3px var(--signal)}
378
+ to{box-shadow:0 0 0 3px transparent}
379
+ }
380
+
370
381
  .gruppenkopf{display:block;margin:16px 0 0;padding:0 0 9px;font-size:19px;font-weight:800;letter-spacing:.2em;color:var(--tinte);border-bottom:2px solid var(--tinte)}
371
382
  .gruppenkopf:first-child{margin-top:0}
372
383
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hmgr",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "Harness Manager: Skills und Plugins für AI-Harnesses verwalten",
5
5
  "license": "MIT",
6
6
  "author": "Tim Kaufmann",