mnfst 0.5.199-next.4 → 0.5.199-next.5

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.199-next.4';
5
+ const MANIFEST_BUILD_VERSION = '0.5.199-next.5';
6
6
 
7
7
  /* Manifest Data Sources - Appwrite Integration */
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.199-next.4';
5
+ const MANIFEST_BUILD_VERSION = '0.5.199-next.5';
6
6
 
7
7
  /* Manifest Data Sources - Configuration */
8
8
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest.appwrite.auth.js": "sha384-HMJmRXnvdDBWyuteXL+DTxWM2llSCdjl4EDamEI7iHNAVlqeOCE4GDTm8ZHipPRj",
3
- "manifest.appwrite.data.js": "sha384-avAqJJn65+6k2tbvGja/S0ZsEOtCEhFG93LgqODbuaP4kLaWFkVrZQwnnPJLxbIR",
3
+ "manifest.appwrite.data.js": "sha384-DUWARP4Dm+Oclucl91j4U75XrEaV7o1yDDCoaGdfwy+IOhTJhZCZQSu/T8sosFiQ",
4
4
  "manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
5
5
  "manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
6
6
  "manifest.charts.js": "sha384-qQrjhWkIDi4oU5uQrpy8DdlMjY+gfiivBuX6mssNiFdkawgkVBvKneGgp+nd3PYJ",
@@ -11,7 +11,7 @@
11
11
  "manifest.combobox.js": "sha384-SN3XHumoebxcduFQqx5zt65/Cd10y8nuf9Gkco6UnsZ42sadRd+gvVLfWPdkkuPH",
12
12
  "manifest.components.js": "sha384-DLGSqCbnGkS87AWJ8GpKoAFP4nCosgC5SFSg6C10hH/nWu/MxI+0ntHlnFEfs++u",
13
13
  "manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
14
- "manifest.data.js": "sha384-Cfww6fFCDaSckoFAUvLRf7ResiGMSmmLgNeZQLe3JbqHZufiwbhRMzTyw3FaOL0l",
14
+ "manifest.data.js": "sha384-M6CAMnitIXuxwzHWKHREyHtWHwLL/8sa0eIRxmDQcQiob1lZC2wTzLUeHxM1cdOX",
15
15
  "manifest.datepicker.js": "sha384-8vTJYkLqqvgXgm1w1becuaRR/jUEpiLBjBJ6RsRbUr+ZkypvspD6EsssuDrX4SQq",
16
16
  "manifest.defer.js": "sha384-wXa60M2OHigi5rBurXQ7Oa8eyr5WTKs7EUn2GOru62KMdfs/LDyNegxCc9MNJFSe",
17
17
  "manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
@@ -19,7 +19,7 @@
19
19
  "manifest.edit.js": "sha384-oLsrH7/UkJ/KEl3UO77hexcf2rYCGgzXZd0fe2EMq3hKRE7aUqLiEnLJhSV7zTaY",
20
20
  "manifest.export.js": "sha384-HCy3n6ifrcAGIyzTRgZ5JIRvSX9q9/8ykMKRSbt1QTnhXz6PgYlV0AE2N2Yi2re+",
21
21
  "manifest.icons.js": "sha384-kKOINpX70YzNO3FvkV6umrmO3f0KIu0DNbwB+ej55a7y/lhU7v/myQtXL0bwCTbt",
22
- "manifest.localization.js": "sha384-p8MRlfpjQuMar7rerGRjfdRKqIPoU/QOwVmnwN+PjhrlEZNpetYLZhqO2i5BZn9s",
22
+ "manifest.localization.js": "sha384-EImsIw8Z3l13V5eH9ZJF/yfsOfIvR1oPRNSArANkPji7V8O3IRf6zMwsNiAGj3Hk",
23
23
  "manifest.markdown.js": "sha384-HFE2VwBRX5vhDGQ3c8XgZ10Y5ENkXxHzgX9DjmIEffqLoUDbnJYNk3TFWT9k3e+f",
24
24
  "manifest.native.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
25
25
  "manifest.payments.js": "sha384-8pofk2zhxtapPMpwnFyTelPMrYhShNhDCE722uQh/EYFvZFuPLmSdgunqsarVQjv",
@@ -28,14 +28,14 @@
28
28
  "manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
29
29
  "manifest.status.js": "sha384-nwvUSbT+gpRMqhYx6MeBYswUMH72rY92mjgxv0vrSG7Ht7l83XYpEB6fCTbXjjfK",
30
30
  "manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
31
- "manifest.sw.js": "sha384-evmpNHyxUu1Qsu2FP7buzG48O5Ppbf0ZlCn8V4Br4LiU6qrNCkfqh1yXxuafEg6Z",
31
+ "manifest.sw.js": "sha384-YKNlPL6G+eVU2hMofRXqfn2HVTJtnbfW6ru+SskVOiLPcN02j6nSOfqkCd+Qkz/3",
32
32
  "manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
33
33
  "manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
34
34
  "manifest.text.edit.js": "sha384-pfhc4IPKNJokj6ldlgD+KwGLN4UoM21CNeVf2sVq/34X1Un2PUhEuyTg5itmtsXS",
35
35
  "manifest.toasts.js": "sha384-/OV32LLMJl0Iw8NoNQJ+5lgntLxjIJq/YNj5JAKS/+nUhVx4MMB9h1FOqP7zuj2M",
36
36
  "manifest.tooltips.js": "sha384-uXVVQADl6pY2Pj9i/UaSclZN7dzbKhLkmevv7Ad0+wXR8LgqodALNFrc5jZJjlwm",
37
37
  "manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
38
- "manifest.utilities.js": "sha384-RvUXxxMLv5q6Psng6jjfK5O1xw05mV6BdLjV92zQW7Zuy3XWctl3QnF5XYNuPyYq",
38
+ "manifest.utilities.js": "sha384-guy9W9zXKT+LqM9CWfy6Y4poNhZoTF9AZSQgjskryurK2Sgit4Wc8oT513fcOCjO",
39
39
  "manifest.virtual.js": "sha384-YwwmAsZNddajnAkLhBWtrK8edHMJOhD7cJkQi3qmm5o1Thg0wbtDU0dsfrCHKTIQ",
40
- "manifest.js": "sha384-+rWocB3jT+iBErwbS1ZBwt24C/jhUavBduc5EzMSu8ZZooyO52LeGpW7UscLdLYx"
40
+ "manifest.js": "sha384-z6bjoNHfkJwD3bRcjChzuzM3y/3LrWVfc8nsApLtSXcshgJc8L4+Vz7XaV3MN6N8"
41
41
  }
package/lib/manifest.js CHANGED
@@ -388,8 +388,14 @@
388
388
  return false;
389
389
  }
390
390
 
391
+ // One fetch per URL: concurrent callers share the first call's promise
392
+ const inflightScripts = new Map();
393
+
391
394
  function injectScript(url) {
392
- return new Promise((resolve, reject) => {
395
+ const shared = inflightScripts.get(url);
396
+ if (shared) return shared;
397
+
398
+ const promise = new Promise((resolve, reject) => {
393
399
  // Same src already in the DOM (author tag, prerendered HTML, second loader run)
394
400
  const existing = document.querySelector(`script[src="${url}"]`);
395
401
  if (existing) {
@@ -412,6 +418,10 @@
412
418
  script.onerror = () => { script.removeAttribute('data-mnfst-loading'); script.remove(); reject(new Error(`Failed to load ${url}`)); };
413
419
  document.head.appendChild(script);
414
420
  });
421
+
422
+ inflightScripts.set(url, promise);
423
+ promise.then(() => inflightScripts.delete(url), () => inflightScripts.delete(url)); // dedupe only while in flight
424
+ return promise;
415
425
  }
416
426
 
417
427
  // Load a plugin, falling through the CDN chain on error. A fallback insert
@@ -260,15 +260,19 @@ function initializeLocalizationPlugin() {
260
260
 
261
261
  try {
262
262
  let manifest = window.__manifestLoaded || window.ManifestComponentsRegistry?.manifest;
263
- // A partially-seeded global can be truthy yet lack `data` — fetch the
264
- // real manifest so locale detection sees every source.
263
+ // A partially-seeded global can be truthy yet lack `data` — await the
264
+ // shared in-flight fetch (or start one, one request per boot) so locale
265
+ // detection sees every source.
265
266
  if (!manifest || !manifest.data) {
266
- const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
267
- const response = await fetch(manifestUrl);
268
- if (!response.ok) {
269
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
267
+ if (window.__manifestPromise) manifest = await window.__manifestPromise.catch(() => null);
268
+ if (!manifest || !manifest.data) {
269
+ const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
270
+ window.__manifestPromise = fetch(manifestUrl).then(r => {
271
+ if (!r.ok) throw new Error(`HTTP ${r.status}: ${r.statusText}`);
272
+ return r.json();
273
+ }).then(m => { window.ManifestDataConfig?.interpolateManifest?.(m); return m; });
274
+ manifest = await window.__manifestPromise;
270
275
  }
271
- manifest = await response.json();
272
276
  }
273
277
 
274
278
  if (!manifest || typeof manifest !== 'object') {
@@ -16,7 +16,7 @@
16
16
 
17
17
  // Build stamps the published version here; unstamped (dev) falls back to the
18
18
  // registration URL's `v`, then 'dev'.
19
- const BUILD_VERSION = '0.5.199-next.4';
19
+ const BUILD_VERSION = '0.5.199-next.5';
20
20
  const CACHE_PREFIX = 'mnfst-sw:';
21
21
  const META_CACHE = CACHE_PREFIX + 'meta';
22
22
  const SWR_CAP = 200;
@@ -471,6 +471,7 @@ class TailwindCompiler {
471
471
  this.compileTimeout = null;
472
472
  this.cache = new Map();
473
473
  this.hasInitialized = true;
474
+ this.staticUtilitiesCoveredClasses = null;
474
475
  // manifest.code.js (and others) may still register ignore rules; mirror full constructor defaults.
475
476
  this.ignoredClassPatterns = [
476
477
  /^hljs/, /^language-/, /^copy$/, /^copied$/, /^lines$/, /^selected$/
@@ -555,6 +556,11 @@ class TailwindCompiler {
555
556
  // Cache for parsed class names (must be before addCriticalBlockingStylesSync)
556
557
  this.classCache = new Map();
557
558
 
559
+ // Read any publish/render-provided static utilities sheet before the
560
+ // first compile — its classes are skipped rather than regenerated.
561
+ this.staticUtilitiesCoveredClasses = null;
562
+ this.detectStaticUtilitiesSheet();
563
+
558
564
  // Add critical styles IMMEDIATELY - don't wait for anything
559
565
  this.addCriticalBlockingStylesSync();
560
566
 
@@ -1194,7 +1200,11 @@ TailwindCompiler.prototype.loadAndApplyCache = function () {
1194
1200
 
1195
1201
  if (cacheToUse && cacheToUse.css) {
1196
1202
  const applyCacheStart = performance.now();
1197
- this.styleElement.textContent = cacheToUse.css;
1203
+ // The cached CSS may predate the static utilities sheet (or come
1204
+ // from a visitor without one) — strip anything it already covers
1205
+ // so we never re-emit those rules.
1206
+ const cachedCss = this.stripCoveredRulesFromCss(cacheToUse.css);
1207
+ this.styleElement.textContent = cachedCss;
1198
1208
  this.ensureUtilityStylesLast();
1199
1209
  this.scheduleEnsureUtilityStylesLast();
1200
1210
  this.lastThemeHash = cacheToUse.themeHash;
@@ -1202,7 +1212,7 @@ TailwindCompiler.prototype.loadAndApplyCache = function () {
1202
1212
  // Also apply cache to critical style element
1203
1213
  // Extract utilities from @layer utilities block and apply directly (no @layer)
1204
1214
  if (this.criticalStyleElement && !this.criticalStyleElement.textContent) {
1205
- let criticalCss = cacheToUse.css;
1215
+ let criticalCss = cachedCss;
1206
1216
  // Remove @layer utilities wrapper if present
1207
1217
  criticalCss = criticalCss.replace(/@layer\s+utilities\s*\{/g, '').replace(/\}\s*$/, '').trim();
1208
1218
  if (criticalCss) {
@@ -1590,7 +1600,7 @@ TailwindCompiler.prototype.getUsedClasses = function () {
1590
1600
  }
1591
1601
 
1592
1602
  const result = {
1593
- classes: Array.from(allClasses),
1603
+ classes: this.filterStaticallyCoveredClasses(Array.from(allClasses)),
1594
1604
  variableSuffixes: Array.from(usedVariableSuffixes)
1595
1605
  };
1596
1606
 
@@ -3185,9 +3195,10 @@ TailwindCompiler.prototype.compile = async function () {
3185
3195
  this.currentThemeVars.set(name, value);
3186
3196
  }
3187
3197
 
3188
- // Generate utilities for all static classes
3198
+ // Generate utilities for all static classes (minus anything the
3199
+ // static utilities sheet already covers).
3189
3200
  const staticUsedData = {
3190
- classes: Array.from(this.staticClassCache),
3201
+ classes: this.filterStaticallyCoveredClasses(Array.from(this.staticClassCache)),
3191
3202
  variableSuffixes: []
3192
3203
  };
3193
3204
  // Process static classes for variable suffixes
@@ -3345,6 +3356,191 @@ TailwindCompiler.prototype.compile = async function () {
3345
3356
 
3346
3357
 
3347
3358
 
3359
+ // Static utilities sheet detection
3360
+ // Publish/render may ship a precompiled utilities sheet — `<link rel="stylesheet"
3361
+ // data-mnfst-utilities>` or `<style data-mnfst-utilities>`. Read the classes it
3362
+ // already covers once so compile() only generates/patches what's left.
3363
+
3364
+ TailwindCompiler.prototype.findStaticUtilitiesElement = function () {
3365
+ try {
3366
+ return document.querySelector('link[rel="stylesheet"][data-mnfst-utilities], style[data-mnfst-utilities]');
3367
+ } catch (e) {
3368
+ return null;
3369
+ }
3370
+ };
3371
+
3372
+ // Top-level selector text only (skips declaration bodies, so values like
3373
+ // `margin: .5rem` can't be mistaken for a `.5` class selector).
3374
+ TailwindCompiler.prototype.extractSelectorsFromCssText = function (cssText) {
3375
+ const selectors = [];
3376
+ const len = cssText.length;
3377
+ let i = 0;
3378
+ while (i < len) {
3379
+ if (cssText[i] === '/' && cssText[i + 1] === '*') {
3380
+ const end = cssText.indexOf('*/', i + 2);
3381
+ i = end === -1 ? len : end + 2;
3382
+ continue;
3383
+ }
3384
+ if (cssText[i] === '@') {
3385
+ let j = i;
3386
+ while (j < len && cssText[j] !== '{' && cssText[j] !== ';') j++;
3387
+ if (j >= len || cssText[j] === ';') { i = j + 1; continue; }
3388
+ i = j + 1;
3389
+ let depth = 1;
3390
+ const start = i;
3391
+ while (i < len && depth > 0) {
3392
+ if (cssText[i] === '{') depth++;
3393
+ else if (cssText[i] === '}') depth--;
3394
+ i++;
3395
+ }
3396
+ selectors.push(...this.extractSelectorsFromCssText(cssText.slice(start, i - 1)));
3397
+ continue;
3398
+ }
3399
+ const selStart = i;
3400
+ while (i < len && cssText[i] !== '{' && cssText[i] !== '}') i++;
3401
+ if (i >= len || cssText[i] === '}') { i++; continue; }
3402
+ const selector = cssText.slice(selStart, i).trim();
3403
+ i++;
3404
+ let depth = 1;
3405
+ while (i < len && depth > 0) {
3406
+ if (cssText[i] === '{') depth++;
3407
+ else if (cssText[i] === '}') depth--;
3408
+ i++;
3409
+ }
3410
+ if (selector) selectors.push(selector);
3411
+ }
3412
+ return selectors;
3413
+ };
3414
+
3415
+ // Selectors escape every non-alphanumeric/hyphen char (see escapeClassName),
3416
+ // so `.hover\:bg-brand` unescapes back to the `hover:bg-brand` token form
3417
+ // used everywhere else (class attributes, usedClasses, parseClassName).
3418
+ TailwindCompiler.prototype.classNamesFromCssText = function (cssText) {
3419
+ const classSet = new Set();
3420
+ const classRe = /\.((?:\\.|[a-zA-Z0-9_-])+)/g;
3421
+ for (const selector of this.extractSelectorsFromCssText(cssText)) {
3422
+ let m;
3423
+ classRe.lastIndex = 0;
3424
+ while ((m = classRe.exec(selector)) !== null) {
3425
+ classSet.add(m[1].replace(/\\(.)/g, '$1'));
3426
+ }
3427
+ }
3428
+ return classSet;
3429
+ };
3430
+
3431
+ // Read the static sheet's covered classes once. Same-origin `<link>`/`<style>`
3432
+ // rules are read synchronously via CSSOM; if the sheet isn't accessible yet
3433
+ // (cross-origin, or not finished loading) this falls back to fetching the
3434
+ // `href` as text and re-populating the covered set when it resolves.
3435
+ TailwindCompiler.prototype.detectStaticUtilitiesSheet = function () {
3436
+ this.staticUtilitiesCoveredClasses = null;
3437
+ const el = this.findStaticUtilitiesElement();
3438
+ if (!el) return;
3439
+
3440
+ if (el.tagName === 'STYLE') {
3441
+ this.staticUtilitiesCoveredClasses = this.classNamesFromCssText(el.textContent || '');
3442
+ return;
3443
+ }
3444
+
3445
+ try {
3446
+ const sheet = Array.from(document.styleSheets).find(s => s.ownerNode === el);
3447
+ const rules = sheet && sheet.cssRules;
3448
+ if (rules && rules.length > 0) {
3449
+ const cssText = Array.from(rules).map(r => r.cssText).join('\n');
3450
+ this.staticUtilitiesCoveredClasses = this.classNamesFromCssText(cssText);
3451
+ return;
3452
+ }
3453
+ } catch (e) {
3454
+ // Cross-origin (or not yet parsed) — fall through to fetch.
3455
+ }
3456
+
3457
+ // Placeholder so callers treat "not yet known" as "nothing covered" rather
3458
+ // than null, until the fetch below resolves and replaces it.
3459
+ this.staticUtilitiesCoveredClasses = new Set();
3460
+ const href = el.getAttribute('href');
3461
+ if (!href || typeof fetch !== 'function') return;
3462
+ fetch(href).then(r => (r.ok ? r.text() : '')).then(text => {
3463
+ if (text) this.staticUtilitiesCoveredClasses = this.classNamesFromCssText(text);
3464
+ }).catch(() => {});
3465
+ };
3466
+
3467
+ // Drop classes already covered by the static sheet before generating rules
3468
+ // for them again — the JIT should only ever patch what's left uncovered.
3469
+ TailwindCompiler.prototype.filterStaticallyCoveredClasses = function (classes) {
3470
+ const covered = this.staticUtilitiesCoveredClasses;
3471
+ if (!covered || covered.size === 0) return classes;
3472
+ return classes.filter(c => !covered.has(c));
3473
+ };
3474
+
3475
+ // The localStorage cache (manifest.utilities.cache.js) stores a full compiled
3476
+ // stylesheet from a previous visit — one that may predate the static sheet, or
3477
+ // come from a visitor without one. Re-applying it verbatim would re-emit every
3478
+ // rule the static sheet already covers, so strip those rules out first.
3479
+ TailwindCompiler.prototype.stripCoveredRulesFromCss = function (cssText) {
3480
+ const covered = this.staticUtilitiesCoveredClasses;
3481
+ if (!covered || covered.size === 0 || !cssText) return cssText;
3482
+
3483
+ const strip = (text) => {
3484
+ const out = [];
3485
+ const len = text.length;
3486
+ let i = 0;
3487
+ while (i < len) {
3488
+ if (text[i] === '/' && text[i + 1] === '*') {
3489
+ const end = text.indexOf('*/', i + 2);
3490
+ const stop = end === -1 ? len : end + 2;
3491
+ out.push(text.slice(i, stop));
3492
+ i = stop;
3493
+ continue;
3494
+ }
3495
+ if (text[i] === '@') {
3496
+ let j = i;
3497
+ while (j < len && text[j] !== '{' && text[j] !== ';') j++;
3498
+ if (j >= len || text[j] === ';') { out.push(text.slice(i, j + 1)); i = j + 1; continue; }
3499
+ const atHead = text.slice(i, j + 1);
3500
+ i = j + 1;
3501
+ let depth = 1;
3502
+ const start = i;
3503
+ while (i < len && depth > 0) {
3504
+ if (text[i] === '{') depth++;
3505
+ else if (text[i] === '}') depth--;
3506
+ i++;
3507
+ }
3508
+ out.push(`${atHead}${strip(text.slice(start, i - 1))}}`);
3509
+ continue;
3510
+ }
3511
+ const selStart = i;
3512
+ while (i < len && text[i] !== '{' && text[i] !== '}') i++;
3513
+ if (i >= len || text[i] === '}') { i++; continue; }
3514
+ const selector = text.slice(selStart, i).trim();
3515
+ const bodyStart = i;
3516
+ i++;
3517
+ let depth = 1;
3518
+ while (i < len && depth > 0) {
3519
+ if (text[i] === '{') depth++;
3520
+ else if (text[i] === '}') depth--;
3521
+ i++;
3522
+ }
3523
+ const fullRule = selector + text.slice(bodyStart, i);
3524
+ // Match tokens directly against the bare selector text — it has no
3525
+ // rule body for classNamesFromCssText's selector-then-`{` scan to key off.
3526
+ const classes = [];
3527
+ const classRe = /\.((?:\\.|[a-zA-Z0-9_-])+)/g;
3528
+ let cm;
3529
+ while ((cm = classRe.exec(selector)) !== null) {
3530
+ classes.push(cm[1].replace(/\\(.)/g, '$1'));
3531
+ }
3532
+ // Only drop a rule once every class it references is covered — a
3533
+ // mixed selector (`:where(.row, .col)`) stays if either is new.
3534
+ const isFullyCovered = classes.length > 0 && classes.every(c => covered.has(c));
3535
+ if (!isFullyCovered) out.push(fullRule);
3536
+ }
3537
+ return out.join('\n');
3538
+ };
3539
+
3540
+ return strip(cssText);
3541
+ };
3542
+
3543
+
3348
3544
  // DOM observation: watch for changes and trigger recompilation
3349
3545
 
3350
3546
  // Setup component load listener and MutationObserver