mnfst 0.5.163 → 0.5.164

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.
@@ -1,17 +1,12 @@
1
1
  /* Manifest Status — config
2
2
  /* By Andrew Matlock under MIT license
3
3
  /* https://manifestx.dev
4
- /*
5
- /* Reads the top-level `status` block from manifest.json and normalizes each
6
- /* named entry into { signals[], rollup, refresh, ... }. Pure signal layer —
7
- /* no UI. Each entry resolves to $status.<name> (see manifest.status.main.js).
8
4
  */
9
5
 
10
6
  (function () {
11
7
  'use strict';
12
8
 
13
- // Infer a signal's provider type from which field is present (mirrors the
14
- // data plugin's field-presence inference).
9
+ // Infer provider type from which field is present.
15
10
  function normalizeSignal(sig) {
16
11
  if (typeof sig === 'string') return { type: 'probe', url: sig, label: sig };
17
12
  if (!sig || typeof sig !== 'object') return { type: 'unknown', label: 'unknown' };
@@ -50,7 +45,7 @@
50
45
  if (def.confirmations) opts.confirmations = def.confirmations;
51
46
  if (def.staleAfter) opts.staleAfter = def.staleAfter;
52
47
  } else {
53
- // Single-signal object (carries its own provider field).
48
+ // Single-signal object.
54
49
  signals = [normalizeSignal(def)];
55
50
  if (def.refresh) opts.refresh = def.refresh;
56
51
  }
@@ -61,9 +56,8 @@
61
56
  return { name, signals, ...opts };
62
57
  }
63
58
 
64
- // Resolve manifest.json. Reuse a cached copy only if it actually carries the
65
- // `status` block — other plugins may cache a normalized manifest that omits
66
- // keys they don't consume, so fall through to a fresh fetch otherwise.
59
+ // Reuse a cached manifest only if it carries the `status` block; some plugins
60
+ // cache a normalized copy that omits keys they don't consume.
67
61
  async function ensureStatusManifest() {
68
62
  const cached = window.ManifestComponentsRegistry?.manifest || window.__manifestLoaded;
69
63
  if (cached && cached.status) return cached;
@@ -93,16 +87,12 @@
93
87
  /* Manifest Status — store
94
88
  /* By Andrew Matlock under MIT license
95
89
  /* https://manifestx.dev
96
- /*
97
- /* Internal reactive store backing $status. Mirrors store('data'):
98
- /* named entries + a _version counter bumped on every update.
99
90
  */
100
91
 
101
92
  (function () {
102
93
  'use strict';
103
94
 
104
- // Ordered severity. maintenance sits between operational and degraded so a
105
- // worst-of rollup surfaces it over a fully-operational set. unknown = -1.
95
+ // Ordered severity; maintenance sits between operational and degraded.
106
96
  const LEVELS = {
107
97
  operational: 0,
108
98
  maintenance: 0.5,
@@ -134,11 +124,6 @@
134
124
  /* Manifest Status — signal resolvers + rollup
135
125
  /* By Andrew Matlock under MIT license
136
126
  /* https://manifestx.dev
137
- /*
138
- /* Each provider type resolves a normalized signal to { state, latencyMs }.
139
- /* Generic providers (probe/feed/static/heartbeat) are fully implemented;
140
- /* appwrite resolves via the public /health endpoint; mirror uses a small
141
- /* registry of known upstream feeds; mcp is feed-based and pluggable.
142
127
  */
143
128
 
144
129
  (function () {
@@ -183,8 +168,7 @@
183
168
  return { state, latencyMs };
184
169
  } catch (err) {
185
170
  clearTimeout(t);
186
- // Network error / CORS / timeout: can't distinguish down from blocked,
187
- // so report unknown rather than a false outage.
171
+ // Can't distinguish down from CORS-blocked, so report unknown not outage.
188
172
  return { state: 'unknown', latencyMs: null, error: err && err.name ? err.name : 'fetch failed' };
189
173
  }
190
174
  }
@@ -194,13 +178,11 @@
194
178
  const res = await fetch(sig.url, { cache: 'no-store' });
195
179
  if (!res.ok) return { state: 'unknown', latencyMs: null, error: 'feed ' + res.status };
196
180
  const json = await res.json();
197
- // The feed node may be a bare state string, or an object carrying
198
- // both a state and a human update message.
181
+ // Feed node may be a bare state string or an object with state + message.
199
182
  const node = sig.path ? dig(json, sig.path) : json;
200
183
  const obj = (node && typeof node === 'object') ? node : null;
201
184
  const raw = obj ? (obj.state != null ? obj.state : obj.status) : node;
202
185
  const message = (obj && obj.message) ? obj.message : (json.message || null);
203
- // Optional historical data a backend can hydrate.
204
186
  const history = Array.isArray(obj && obj.history) ? obj.history : (Array.isArray(json.history) ? json.history : undefined);
205
187
  const uptime = (obj && typeof obj.uptime === 'number') ? obj.uptime : (typeof json.uptime === 'number' ? json.uptime : undefined);
206
188
  const incidents = Array.isArray(obj && obj.incidents) ? obj.incidents : (Array.isArray(json.incidents) ? json.incidents : undefined);
@@ -324,21 +306,14 @@
324
306
  /* Manifest Status — main
325
307
  /* By Andrew Matlock under MIT license
326
308
  /* https://manifestx.dev
327
- /*
328
- /* Wires the store, polls each entry's signals, and registers the $status
329
- /* magic. $status.<name> → rolled-up health object; $status.overall → worst
330
- /* across all entries. The plugin renders nothing: the author drives their own
331
- /* UI from these reactive values (:class, x-show, x-text, etc.).
332
309
  */
333
310
 
334
311
  (function () {
335
312
  'use strict';
336
313
 
337
- /* Shared global: ManifestUI — universal `_ui` text resolver. Defined once
338
- and shared across plugins (datepicker/colorpicker/charts/status); the
339
- `if (!window.ManifestUI)` guard means whichever loads first wins. Lets
340
- baked-in labels be localized/overridden via any loaded data source's
341
- `_ui` key, locale-reactive. Kept behaviourally identical across copies. */
314
+ /* Shared `_ui` text resolver defined once across plugins, first load wins.
315
+ Localizes baked-in labels via any data source's `_ui` key. Kept identical
316
+ across copies. */
342
317
  if (!window.ManifestUI) {
343
318
  window.ManifestUI = {
344
319
  _loadedSourceNames() {
@@ -388,9 +363,8 @@
388
363
 
389
364
  function store() { return window.Alpine && window.Alpine.store('status'); }
390
365
 
391
- // Debounce state changes: a new state must be observed `confirmations`
392
- // times in a row before it replaces the committed state. confirmations:1
393
- // (default) commits immediately.
366
+ // Debounce: a new state must be observed `confirmations` times in a row
367
+ // before it replaces the committed one (default 1 = commit immediately).
394
368
  function applyHysteresis(entry, observed) {
395
369
  const need = entry.confirmations || 1;
396
370
  const prev = committed[entry.name];
@@ -450,8 +424,7 @@
450
424
 
451
425
  const rolled = Signals.rollup(entry, resolved);
452
426
 
453
- // Manual override wins and bypasses hysteresis; otherwise debounce the
454
- // observed state through applyHysteresis.
427
+ // Manual override wins and bypasses hysteresis.
455
428
  let state, message = null;
456
429
  if (overrides[entry.name]) {
457
430
  state = overrides[entry.name].state;
@@ -474,8 +447,7 @@
474
447
  hist = rolled.history.slice(-entry.history);
475
448
  while (hist.length < entry.history) hist.unshift('unknown');
476
449
  } else {
477
- // Pre-fill with `unknown` so the bar is always full width and fills
478
- // from the right as checks accrue (StatusPage-style no-data segments).
450
+ // Pre-fill with `unknown` so the bar is full-width and fills from the right.
479
451
  hist = histories[entry.name] ? histories[entry.name].slice() : new Array(entry.history).fill('unknown');
480
452
  hist.push(state);
481
453
  if (hist.length > entry.history) hist = hist.slice(hist.length - entry.history);
@@ -532,8 +504,7 @@
532
504
  return known.reduce((a, b) => (level(b.state) > level(a.state) ? b : a)).state;
533
505
  }
534
506
 
535
- // Default English labels per state; overridable per-locale via any loaded
536
- // data source's `_ui.status.label` (same mechanism as datepicker/colorpicker).
507
+ // Default English labels; overridable per-locale via a data source's `_ui.status.label`.
537
508
  const UI_FALLBACK = {
538
509
  label: {
539
510
  operational: 'Operational',
@@ -545,9 +516,7 @@
545
516
  }
546
517
  };
547
518
 
548
- // Resolve `$x`/`$locale`/`${…}` reference strings in `_ui` values (same
549
- // treatment as the colorpicker's _resolveRefString — kept semantically
550
- // identical). Reading inside the caller's effect registers the deps.
519
+ // Resolve `$x`/`$locale`/`${…}` reference strings in `_ui` values.
551
520
  function _resolveRefString(val) {
552
521
  if (typeof val !== 'string' || val.length === 0) return val;
553
522
  const trimmed = val.trim();
@@ -566,13 +535,9 @@
566
535
  return val;
567
536
  }
568
537
 
569
- // Human label for a state string, localized through `_ui` when available.
570
- // Falls back to the English default, then a generic title-case for any
571
- // custom/unrecognized state.
538
+ // Human label for a state, localized through `_ui`, else title-cased.
572
539
  function formatStateLabel(state) {
573
- // Dep on the data store version so labels re-resolve when a `_ui`
574
- // source loads late or reloads on locale switch (same fix as the
575
- // datepicker's ui effect).
540
+ // Dep on the data store version so labels re-resolve when a `_ui` source loads late.
576
541
  try { void (window.Alpine && Alpine.store('data')?._dataVersion); } catch (_) { }
577
542
  const ui = window.ManifestUI ? window.ManifestUI.resolve('status', UI_FALLBACK) : UI_FALLBACK;
578
543
  const labels = (ui && ui.label) || UI_FALLBACK.label;
@@ -580,15 +545,14 @@
580
545
  return String(state == null ? '' : state).replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
581
546
  }
582
547
 
583
- // Reserved accessor names — never treated as entry names or enumerated, so
584
- // `x-for="(service, name) in $status"` iterates only real services.
548
+ // Reserved accessor names — never treated as entries or enumerated.
585
549
  const RESERVED = new Set(['overall', 'all', 'ready', 'incidents', 'label', 'set', 'clear', 'refresh', 'beat']);
586
550
 
587
551
  function setOverride(name, state, message) {
588
552
  overrides[name] = { state, message: message || null };
589
553
  committed[name] = state;
590
554
  delete pending[name];
591
- // An operator update is an incident: open/append, or resolve when set back to operational.
555
+ // An operator update is an incident: open/append, or resolve when back to operational.
592
556
  if (state === 'operational') { if (message) logIncident(name, state, message); resolveIncident(name); }
593
557
  else { logIncident(name, state, message); }
594
558
  const e = config && config.entries[name];
@@ -2,19 +2,10 @@
2
2
 
3
3
  const svgCache = new Map();
4
4
 
5
- // Shared DOMPurify loader (only fetched when the .safe modifier is used).
6
- // SVG natively supports <script>, on* event handlers, javascript: URLs in
7
- // href, and <foreignObject> with HTML inside any of which become an XSS
8
- // vector when the SVG source is user-supplied. The .safe modifier opts into
9
- // DOMPurify with the SVG profile so authors rendering user-uploaded SVGs
10
- // (e.g. profile avatars from an Appwrite bucket) don't have to write
11
- // per-source sanitization.
12
- //
13
- // Defined on window so manifest.markdown.js can share the same loader and
14
- // in-flight promise — otherwise both plugins' top-level `let purifyPromise`
15
- // declarations collide in the realm's global lexical env and the second
16
- // script throws SyntaxError ("Identifier 'purifyPromise' has already been
17
- // declared") at parse time, taking the whole plugin offline.
5
+ // Shared DOMPurify loader for the .safe modifier (user-supplied SVG can carry
6
+ // <script>, on* handlers, javascript: URLs, foreignObject HTML). On window so
7
+ // manifest.markdown.js shares one loader/promiseseparate top-level `let`
8
+ // declarations would collide in the global lexical env and throw at parse time.
18
9
  if (!window.ManifestDOMPurify) {
19
10
  window.ManifestDOMPurify = {
20
11
  _promise: null,
@@ -23,8 +14,7 @@ if (!window.ManifestDOMPurify) {
23
14
  if (this._promise) return this._promise;
24
15
  this._promise = new Promise((resolve, reject) => {
25
16
  const script = document.createElement('script');
26
- // Pinned + Subresource Integrity (see manifest.markdown.js) a
27
- // floating `@latest` or tampered CDN file can't inject JS here.
17
+ // Pinned + SRI so a tampered/floating CDN file can't inject JS.
28
18
  script.src = 'https://cdn.jsdelivr.net/npm/dompurify@3.4.10/dist/purify.min.js';
29
19
  script.integrity = 'sha384-eguRoJERj8ghOpzO//Rl7+ScQsQIR1cH+ajll7+fG+IpbNPlkZsQn9h8ccr+wPXx';
30
20
  script.crossOrigin = 'anonymous';
@@ -47,10 +37,7 @@ if (!window.ManifestDOMPurify) {
47
37
  };
48
38
  }
49
39
 
50
- // Return a sanitized SVG string when the .safe modifier is on; pass through
51
- // otherwise. Uses DOMPurify's SVG profile which strips <script>, on* event
52
- // handlers, javascript: URLs, and dangerous foreignObject HTML — while
53
- // keeping the visual SVG markup intact.
40
+ // Sanitize with DOMPurify's SVG profile when .safe is on; pass through otherwise.
54
41
  async function maybeSanitizeSvg(svgText, safe) {
55
42
  if (!safe) return svgText;
56
43
  try {
@@ -190,10 +177,8 @@ async function initializeSvgPlugin() {
190
177
  return;
191
178
  }
192
179
 
193
- // Opt-in DOMPurify sanitization for user-supplied SVG. Default is
194
- // unsanitized Manifest keeps full SVG fidelity (filters, scripts
195
- // used for animations, etc.). Use .safe when the SVG source can
196
- // come from untrusted parties (uploaded avatars, third-party APIs).
180
+ // .safe opts into sanitization; default keeps full SVG fidelity (filters,
181
+ // animation scripts). Use it when the source is untrusted.
197
182
  const safe = Array.isArray(modifiers) && modifiers.includes('safe');
198
183
 
199
184
  const hasBakedContent =
@@ -268,8 +253,7 @@ async function initializeSvgPlugin() {
268
253
 
269
254
  let svgPluginInitialized = false;
270
255
 
271
- // True once Alpine has completed its initial DOM walk. Listener is bound at
272
- // module load so we never miss the event, whatever the script order.
256
+ // True once Alpine finished its initial walk. Bound at load so we never miss it.
273
257
  let svgAlpineHasWalked = false;
274
258
  document.addEventListener('alpine:initialized', () => { svgAlpineHasWalked = true; });
275
259
 
@@ -284,11 +268,9 @@ async function ensureSvgPluginInitialized() {
284
268
  svgPluginInitialized = true;
285
269
  await initializeSvgPlugin();
286
270
 
287
- // Only walk existing [x-svg] subtrees ourselves when Alpine has ALREADY
288
- // finished its initial walk (i.e. this plugin loaded late). Otherwise we
289
- // register the directive during `alpine:init` and let Alpine's one boot walk
290
- // process every element with all sibling directives already registered —
291
- // walking here during boot would drop nested plugin content.
271
+ // Walk existing [x-svg] subtrees only if Alpine already finished its boot walk
272
+ // (late load). During boot, let Alpine's own walk handle them with all sibling
273
+ // directives registered walking here would drop nested plugin content.
292
274
  if (svgAlpineHasWalked && typeof window.Alpine.initTree === 'function') {
293
275
  const existing = document.querySelectorAll('[x-svg]');
294
276
  existing.forEach((el) => {
@@ -1,15 +1,4 @@
1
- /* Manifest Tooltips — singleton architecture.
2
- *
3
- * Instead of creating one <div popover="hint"> per x-tooltip trigger, this plugin
4
- * maintains ONE tooltip element per popover host (usually just document.body plus
5
- * optionally one per open popover). Every trigger with x-tooltip becomes a
6
- * lightweight content provider that asks the shared controller to show its text,
7
- * anchored to that trigger.
8
- *
9
- * Why: N triggers × 1 tooltip each = N extra DOM nodes that are empty 99% of the
10
- * time. For dense UIs like colorpicker libraries (~300+ swatches × 20 pickers),
11
- * this is the difference between a usable page and a laggy one.
12
- */
1
+ /* Manifest Tooltips */
13
2
 
14
3
  // Hover delay from CSS var (with time-unit parsing). Defaults to 500ms.
15
4
  function getTooltipHoverDelay(element) {
@@ -36,9 +25,8 @@ function getTooltipHostForTrigger(triggerEl) {
36
25
 
37
26
  function initializeTooltipPlugin() {
38
27
 
39
- // Chain mode: if another tooltip was dismissed this recently, the next one
40
- // shows immediately (no hover delay). Also used to skip the hide-show flicker
41
- // when gliding across many triggers — the singleton just re-anchors.
28
+ // Chain mode: a recently-dismissed tooltip lets the next show immediately (no
29
+ // delay), and gliding across triggers re-anchors the singleton without flicker.
42
30
  const TOOLTIP_CHAIN_GRACE_MS = 250;
43
31
  let _lastTooltipHideTime = 0;
44
32
  const markTooltipHidden = () => { _lastTooltipHideTime = Date.now(); };
@@ -46,11 +34,9 @@ function initializeTooltipPlugin() {
46
34
  const isInChainWindow = () => (Date.now() - _lastTooltipHideTime) < TOOLTIP_CHAIN_GRACE_MS;
47
35
 
48
36
  // ---- Singletons per host ----
49
- //
50
- // Most pages only need one singleton (under document.body). Open popovers (menus,
51
- // dialogs) require their own singleton because CSS anchor positioning can't resolve
52
- // across the top-layer boundary. We create them lazily on first use and keep them
53
- // (small, hidden <div>s) for the life of the host.
37
+ // One under document.body; open popovers (menus, dialogs) need their own because
38
+ // CSS anchor positioning can't resolve across the top-layer boundary. Created
39
+ // lazily, kept for the host's life.
54
40
  const _singletons = new WeakMap();
55
41
 
56
42
  function getSingleton(host) {
@@ -87,12 +73,8 @@ function initializeTooltipPlugin() {
87
73
  }
88
74
 
89
75
  // ---- Controller ----
90
- //
91
- // Single pending-show timer shared across the whole plugin. If a trigger arms a
92
- // show and the user moves to another trigger before it fires, the first timer is
93
- // cancelled in favor of the new one. If the singleton is already visible, the new
94
- // trigger updates it in place (chain mode) — no hide/show flicker.
95
-
76
+ // One shared pending-show timer: moving to another trigger cancels the first. If
77
+ // the singleton is already visible, the new trigger updates it in place (chain mode).
96
78
  let _showTimer = null;
97
79
  let _pendingTrigger = null;
98
80
  // Hide is deferred briefly so an incoming show on a different trigger can take
@@ -109,9 +91,8 @@ function initializeTooltipPlugin() {
109
91
  }
110
92
 
111
93
 
112
- // Update the singleton to point at a trigger (anchor, content, classes) and show it.
113
- // Switches between triggers happen by re-anchoring no positional animation. Any
114
- // previous transform state is cleared so the tooltip sits squarely at its anchor.
94
+ // Point the singleton at a trigger (anchor, content, classes) and show it. Switches
95
+ // re-anchor with no animation; residual transform state is cleared.
115
96
  function showSingletonFor(trigger, contentHtml, positions, allowHtml = true) {
116
97
  const host = getTooltipHostForTrigger(trigger);
117
98
  const s = getSingleton(host);
@@ -126,9 +107,8 @@ function initializeTooltipPlugin() {
126
107
  if (positions.length) s.el.classList.add(positions.join('-'));
127
108
  s.currentPositions = positions;
128
109
 
129
- // Escape by default: dynamic ($x / runtime) content can carry attacker
130
- // markup, so it goes in as TEXT. Author-authored literal HTML and the
131
- // explicit `.html`/`.safe` opt-in render as markup (allowHtml=true).
110
+ // Escape by default (dynamic content can carry attacker markup); author HTML
111
+ // and the .html/.safe opt-in render as markup.
132
112
  if (allowHtml) s.el.innerHTML = contentHtml || '';
133
113
  else s.el.textContent = contentHtml || '';
134
114
 
@@ -139,9 +119,7 @@ function initializeTooltipPlugin() {
139
119
  s.activeTrigger = trigger;
140
120
  s.currentAnchorName = anchorName;
141
121
 
142
- // A11y: link the trigger to the tooltip so screen readers announce the
143
- // tooltip text as a description when the trigger receives focus or hover.
144
- // Per WAI-ARIA, aria-describedby is the standard for this relationship.
122
+ // A11y: aria-describedby links trigger tooltip for screen readers.
145
123
  if (!s.el.id) s.el.id = 'mnfst-tooltip-' + Math.random().toString(36).slice(2, 9);
146
124
  s.el.setAttribute('role', 'tooltip');
147
125
  // Preserve any author-provided aria-describedby so we don't stomp it.
@@ -160,11 +138,10 @@ function initializeTooltipPlugin() {
160
138
  let wasOpen = false;
161
139
  document.querySelectorAll('.tooltip[popover="hint"]:popover-open').forEach(el => {
162
140
  wasOpen = true;
163
- try { el.hidePopover(); } catch {}
141
+ try { el.hidePopover(); } catch { }
164
142
  });
165
- // Restore each tooltip's prior aria-describedby on the trigger it had been
166
- // bound to. We can't reach the trigger from the popover alone, so we walk
167
- // the tooltipped triggers and remove our id from their describedby list.
143
+ // Restore each trigger's prior aria-describedby (can't reach it from the
144
+ // popover, so walk tooltipped triggers).
168
145
  document.querySelectorAll('[aria-describedby]').forEach((el) => {
169
146
  if (!el._tooltipPriorDescribedBy && el._tooltipPriorDescribedBy !== '') return;
170
147
  const prior = el._tooltipPriorDescribedBy;
@@ -172,8 +149,8 @@ function initializeTooltipPlugin() {
172
149
  else el.removeAttribute('aria-describedby');
173
150
  el._tooltipPriorDescribedBy = undefined;
174
151
  });
175
- // Only arm the chain window when something was actually open marking
176
- // unconditionally let a plain click fast-track the next focus show.
152
+ // Only arm the chain window when something was open (else a plain click
153
+ // would fast-track the next focus show).
177
154
  if (wasOpen) markTooltipHidden();
178
155
  }
179
156
 
@@ -187,10 +164,7 @@ function initializeTooltipPlugin() {
187
164
  expression.startsWith('$x.') ||
188
165
  (expression.includes('+') || expression.includes('`') || expression.includes('${'));
189
166
 
190
- // Whether to render the resolved content as HTML. Default false (escape):
191
- // dynamic/runtime content can carry attacker markup. Author-authored
192
- // literal HTML in the attribute, or an explicit `.html`/`.safe` modifier,
193
- // opts into raw markup.
167
+ // Render as HTML? Default false (escape); author literal HTML or .html/.safe opts in.
194
168
  let allowHtml = modifiers.includes('html') || modifiers.includes('safe');
195
169
 
196
170
  if (expression.startsWith('$x.')) {
@@ -204,8 +178,7 @@ function initializeTooltipPlugin() {
204
178
  }
205
179
  });
206
180
  } else if (expression.includes('<') && expression.includes('>')) {
207
- // Literal HTML string typed into the attribute — author-authored, as
208
- // trusted as the surrounding template, so render it as markup.
181
+ // Literal HTML in the attribute — author-authored, trusted as the template.
209
182
  allowHtml = true;
210
183
  const escaped = expression.replace(/'/g, "\\'");
211
184
  getContent = evaluateLater(`'${escaped}'`);
@@ -276,17 +249,15 @@ function initializeTooltipPlugin() {
276
249
  el.addEventListener('mouseenter', requestShow);
277
250
  el.addEventListener('mouseleave', requestHide);
278
251
 
279
- // Keyboard / focus interactions — WCAG 2.1 SC 1.4.13 requires tooltip
280
- // content to be accessible to keyboard users via focus, not hover only.
281
- // Gated on :focus-visible so mouse-click focus doesn't flash the tooltip.
252
+ // Keyboard focus (WCAG 2.1 SC 1.4.13). Gated on :focus-visible so mouse-click
253
+ // focus doesn't flash the tooltip.
282
254
  el.addEventListener('focus', () => {
283
255
  if (el.matches(':focus-visible')) requestShow();
284
256
  });
285
257
  el.addEventListener('blur', requestHide);
286
258
 
287
- // Mousedown/click hides immediately and clears the chain window so a
288
- // synthetic re-hover (e.g. content shifting under the cursor after
289
- // navigation) waits the full delay instead of showing instantly.
259
+ // Mousedown/click hides now and clears the chain window, so a synthetic
260
+ // re-hover (content shifting under the cursor) waits the full delay.
290
261
  const hideOnInteraction = () => {
291
262
  cancelPendingShow();
292
263
  hideAnySingleton();
@@ -306,18 +277,10 @@ function initializeTooltipPlugin() {
306
277
  }, true);
307
278
 
308
279
  // ---- Public programmatic-show API ----
309
- //
310
- // Flash a tooltip in response to an action (e.g. the code plugin's inline
311
- // copy confirmation) without requiring the trigger to carry an x-tooltip
312
- // directive. The trigger element acts as the anchor; the singleton is
313
- // reused, so this respects chain mode / focus behaviour just like a
314
- // hover-shown tooltip would. Auto-hides after `durationMs`.
315
- //
316
- // `positions` accepts the same vocabulary as the x-tooltip directive's
317
- // modifiers — array of any subset of ['top','bottom','start','end',
318
- // 'center','corner']. Joined with '-' to form the position class
319
- // (e.g. ['top','end'] → '.top-end'), matching what `x-tooltip.top.end`
320
- // would emit.
280
+ // Flash a tooltip for an action (e.g. code plugin's copy confirmation) without an
281
+ // x-tooltip directive; the trigger is the anchor, the singleton is reused. Auto-
282
+ // hides after `durationMs`. `positions` is the directive modifier vocabulary
283
+ // (subset of ['top','bottom','start','end','center','corner'], joined with '-').
321
284
  window.ManifestTooltips = window.ManifestTooltips || {};
322
285
  window.ManifestTooltips.showTransient = function (triggerEl, contentHtml, durationMs, positions) {
323
286
  if (!triggerEl) return;