mnfst 0.5.209 → 0.5.210

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.209';
5
+ const MANIFEST_BUILD_VERSION = '0.5.210';
6
6
 
7
7
  /* Manifest Data Sources - Appwrite Integration */
8
8
 
@@ -124,6 +124,11 @@
124
124
  const _registry = Alpine.reactive ? Alpine.reactive({}) : {};
125
125
  let _uid = 0;
126
126
 
127
+ const isDevelopment = window.location.hostname === 'localhost' ||
128
+ window.location.hostname === '127.0.0.1' ||
129
+ window.location.hostname.includes('dev') ||
130
+ window.location.search.includes('debug=true');
131
+
127
132
  const _nullApi = { type: '', series: [], update() { }, redraw() { }, toString() { return ''; }, valueOf() { return ''; } };
128
133
 
129
134
  function findAncestorState(el) { let n = el; while (n) { if (n._chartState) return n._chartState; n = n.parentElement; } return null; }
@@ -228,6 +233,30 @@
228
233
  return cfg;
229
234
  }
230
235
 
236
+ // Deterministic structural signature of a normalized config, for the redraw
237
+ // short-circuit below: sorted object keys (key order doesn't matter), arrays
238
+ // keep position, Dates by epoch ms, typed arrays by value. Functions are
239
+ // omitted — identity isn't a meaningful "value" for a config comparison and
240
+ // authors sometimes pass fresh closures that are behaviorally identical.
241
+ function stableStringify(v, seen) {
242
+ if (v === null || typeof v !== 'object') return typeof v === 'function' ? undefined : JSON.stringify(v);
243
+ if (v instanceof Date) return String(v.getTime());
244
+ if (ArrayBuffer.isView(v)) return '[' + Array.prototype.join.call(v, ',') + ']';
245
+ seen = seen || new Set();
246
+ if (seen.has(v)) return '"[Circular]"'; // config is author data; don't trust it's acyclic
247
+ seen.add(v);
248
+ let out;
249
+ if (Array.isArray(v)) {
250
+ out = '[' + v.map(x => { const s = stableStringify(x, seen); return s === undefined ? 'null' : s; }).join(',') + ']';
251
+ } else {
252
+ const parts = [];
253
+ Object.keys(v).sort().forEach(k => { const s = stableStringify(v[k], seen); if (s !== undefined) parts.push(JSON.stringify(k) + ':' + s); });
254
+ out = '{' + parts.join(',') + '}';
255
+ }
256
+ seen.delete(v);
257
+ return out;
258
+ }
259
+
231
260
  /* ---- SVG helpers -------------------------------------------- */
232
261
  function svg(tag, attrs, parent) {
233
262
  const node = document.createElementNS(SVGNS, tag);
@@ -287,7 +316,7 @@
287
316
  return {
288
317
  get type() { return self.config ? self.config.type : ''; },
289
318
  get series() { return self.config ? self.config.series : []; },
290
- update(cfg) { self.config = normalize(cfg, self.el, self.typeFromModifier); self.draw(); },
319
+ update(cfg) { if (self.applyConfig(normalize(cfg, self.el, self.typeFromModifier))) self.draw(); },
291
320
  redraw() { self.draw(); },
292
321
  toString() { return self.config ? self.config.type : ''; }
293
322
  };
@@ -306,10 +335,17 @@
306
335
  Alpine.effect(() => {
307
336
  // Subscribe to the data store heartbeat so $x loads/locale reloads re-run.
308
337
  try { void Alpine.store('data')?._dataVersion; } catch (_) { }
309
- if (getCfg) getCfg(raw => { self.config = normalize(raw, self.el, self.typeFromModifier); self.schedule(); });
310
- else { self.config = normalize(configFromDom(self.el), self.el, self.typeFromModifier); self.schedule(); }
338
+ // Every effect fire re-evaluates the expression (it may read live reactive
339
+ // state — realtime rows, presence, staged $x hydration — that changes far
340
+ // more often than the resulting config). applyConfig() short-circuits a
341
+ // schedule() when the normalized result is value-identical to last time;
342
+ // the first call always applies (self._sig starts undefined).
343
+ if (getCfg) getCfg(raw => { if (self.applyConfig(normalize(raw, self.el, self.typeFromModifier))) self.schedule(); });
344
+ else if (self.applyConfig(normalize(configFromDom(self.el), self.el, self.typeFromModifier))) self.schedule();
311
345
  });
312
- // Re-render on locale (axis number/date formatting) and container resize.
346
+ // Re-render on locale (axis number/date formatting) and container resize —
347
+ // these change rendering without changing the config, so they bypass the
348
+ // signature check and always schedule.
313
349
  self._onLocale = () => self.schedule();
314
350
  window.addEventListener('localechange', self._onLocale);
315
351
  self._ro = new ResizeObserver(() => self.schedule());
@@ -317,9 +353,35 @@
317
353
  if (self.el.id) _registry[self.el.id] = self.api;
318
354
  },
319
355
 
356
+ // Applies a normalized config, returning whether it actually changed (by
357
+ // structural signature, not reference) — see stableStringify. Always
358
+ // "changes" the first time (this._sig starts undefined) so the first draw
359
+ // is unconditional.
360
+ applyConfig(next) {
361
+ const sig = stableStringify(next);
362
+ const changed = this._sig === undefined || sig !== this._sig;
363
+ if (changed) { this._sig = sig; this.config = next; }
364
+ return changed;
365
+ },
366
+
320
367
  // One draw per tick. setTimeout (not rAF) so backgrounded tabs still draw;
321
368
  // doesn't reset a pending timer, so a high-frequency trigger can't starve it.
322
- schedule() { if (this._t) return; this._t = setTimeout(() => { this._t = null; this.draw(); }, 0); },
369
+ schedule() {
370
+ if (this._t) return;
371
+ // Runaway-redraw guard: a chart that schedules dozens of times a second
372
+ // (usually a config-signature miss, or a map's heavy geometry rebuild —
373
+ // see drawMap) pegs the main thread and shows up to the user as a hung
374
+ // tab, not a chart bug. Warn once per element so it's self-diagnosing.
375
+ if (isDevelopment && !this._warnedRedraw) {
376
+ const now = Date.now();
377
+ if (!this._scheduleWindowStart || now - this._scheduleWindowStart > 1000) { this._scheduleWindowStart = now; this._scheduleWindowCount = 0; }
378
+ if (++this._scheduleWindowCount > 20) {
379
+ this._warnedRedraw = true;
380
+ try { console.warn('[Manifest Charts] "' + (this.el.id || this.expression || 'chart') + '" redrew ' + this._scheduleWindowCount + '+ times within one second — likely a reactive dependency changing on every read (or, for a map, a legitimately busy data feed hitting its per-draw geometry cost).'); } catch (_) { }
381
+ }
382
+ }
383
+ this._t = setTimeout(() => { this._t = null; this.draw(); }, 0);
384
+ },
323
385
 
324
386
  renderError(msg) { this.el.innerHTML = ''; const d = document.createElement('small'); d.textContent = msg; this.el.appendChild(d); },
325
387
 
@@ -921,9 +983,18 @@
921
983
  // (numeric values → heat ramp) or categorically (string values → palette).
922
984
  function drawMap(state, root, width, height, geo) {
923
985
  const cfg = state.config;
924
- const fc = geo.topojson.feature(geo.world, geo.world.objects.countries);
925
- const projection = geo.geoNaturalEarth1().fitSize([width, height], fc);
926
- const path = geo.geoPath(projection);
986
+ // The feature collection, fitted projection, and path generator depend only
987
+ // on the atlas (`geo`) and the element's [width, height] — not on cfg.data —
988
+ // so a data-only redraw (the common case: realtime values ticking in) reuses
989
+ // them instead of re-running topojson.feature/fitSize over ~175 polygons.
990
+ // Cached per element, invalidated when the atlas or size changes.
991
+ let geom = state._mapGeom;
992
+ if (!geom || geom.geo !== geo || geom.width !== width || geom.height !== height) {
993
+ const fc = geo.topojson.feature(geo.world, geo.world.objects.countries);
994
+ const projection = geo.geoNaturalEarth1().fitSize([width, height], fc);
995
+ geom = state._mapGeom = { geo, width, height, fc, projection, path: geo.geoPath(projection) };
996
+ }
997
+ const { fc, projection, path } = geom;
927
998
 
928
999
  // Author data: an object { key: value } or an array of { id|code|name, value } / [key, value].
929
1000
  let entries = [];
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.209';
5
+ const MANIFEST_BUILD_VERSION = '0.5.210';
6
6
 
7
7
  /* Manifest Data Sources - Configuration */
8
8
 
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "manifest.appwrite.auth.js": "sha384-HMJmRXnvdDBWyuteXL+DTxWM2llSCdjl4EDamEI7iHNAVlqeOCE4GDTm8ZHipPRj",
3
- "manifest.appwrite.data.js": "sha384-QPhleLv3IIOHioJFz+aIieAStcvqMApPC68lipDajRqxyym9am6rKFXEkHZTJdGo",
3
+ "manifest.appwrite.data.js": "sha384-kmsw1sYLqTxnkRfh7dt0urLSFU+LOkgHofsL1jYGndFvCizo7wsbQix6vV022/ov",
4
4
  "manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
5
5
  "manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
6
- "manifest.charts.js": "sha384-qQrjhWkIDi4oU5uQrpy8DdlMjY+gfiivBuX6mssNiFdkawgkVBvKneGgp+nd3PYJ",
6
+ "manifest.charts.js": "sha384-WE0pnHK4LBAkFyTw4+R6gheB9NFVJg81ZAG9GwMnbKI5gY3zlJIKM5neMlBUhTso",
7
7
  "manifest.chat.js": "sha384-88DfMfJLoi2oxclVD2iIMDkIGAExMXq5k47mekUredlicLaP3oZY5bwtwqs+TqDF",
8
8
  "manifest.code.js": "sha384-aoQ4E26vrhNe/+tL7K0h90c8/0ies7e8oIR6Ay92he2X3dnWZ+OPcn2Dna8fmc+c",
9
9
  "manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
@@ -11,7 +11,7 @@
11
11
  "manifest.combobox.js": "sha384-5DaKDWkWF7zxS80h7G1AcZG9hr+uGqJ1+B09aaVIVF+z0Qn4j8+9KEg7gys/ABQG",
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-YCCrZNKVJGLpTqE8BpI4Co6/vjyOXATVioLdaLSkKQ5oNKTv3ZFN+CqM7lQmObjI",
14
+ "manifest.data.js": "sha384-U4/sSXZI/DL0X7ymE9KrwAKhIv31pJoEo0YnZBgJTAhHYwUZxhaC7nrhWVj/5a/q",
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",
@@ -28,7 +28,7 @@
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-rHHARfDDIo3jE+zWkse6dVR1EwdUASiDFvlskzn/KjmhC6mJgtlSfLbL+FhWRLz1",
31
+ "manifest.sw.js": "sha384-uUl8PEc1B0HXLOMpjQqF0w+4ybSeyHAAOSXmWQkHH4eO7ZiyU43yIlnEWKpsXlgb",
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",
@@ -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.209';
19
+ const BUILD_VERSION = '0.5.210';
20
20
  const CACHE_PREFIX = 'mnfst-sw:';
21
21
  const META_CACHE = CACHE_PREFIX + 'meta';
22
22
  const SWR_CAP = 200;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.209",
3
+ "version": "0.5.210",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",