mnfst 0.5.196 → 0.5.198-next.0
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/lib/manifest.appwrite.data.js +27 -23
- package/lib/manifest.chat.js +15 -2
- package/lib/manifest.combobox.js +1 -0
- package/lib/manifest.computed.js +52 -0
- package/lib/manifest.d.ts +8 -1
- package/lib/manifest.data.js +513 -784
- package/lib/manifest.datepicker.js +2 -0
- package/lib/manifest.defer.js +268 -0
- package/lib/manifest.dropdowns.js +19 -4
- package/lib/manifest.integrity.json +9 -7
- package/lib/manifest.js +2 -0
- package/package.json +3 -1
package/lib/manifest.data.js
CHANGED
|
@@ -265,6 +265,20 @@ let initializationComplete = false;
|
|
|
265
265
|
let _renderReadyTimer = null;
|
|
266
266
|
const RENDER_READY_QUIET_MS = 150; // quiet time before firing render-ready
|
|
267
267
|
|
|
268
|
+
// Landing queue: network writes coalesce into one store write per frame
|
|
269
|
+
const pendingLandings = [];
|
|
270
|
+
let landingResolvers = [];
|
|
271
|
+
let landingFrame = null;
|
|
272
|
+
let landingTimer = null;
|
|
273
|
+
const LANDING_FALLBACK_MS = 50;
|
|
274
|
+
|
|
275
|
+
// Local writes made while a flush is pending win over it (local-last)
|
|
276
|
+
const pendingLocal = new Map(); // source -> Map<$id, { patch, removed }>
|
|
277
|
+
|
|
278
|
+
// Lazy cross-source `all`
|
|
279
|
+
let allCache = null;
|
|
280
|
+
let allDirty = true;
|
|
281
|
+
|
|
268
282
|
// Deep seal so Alpine won't proxy (double-proxying causes recursion)
|
|
269
283
|
function deepSeal(obj) {
|
|
270
284
|
if (obj === null || typeof obj !== 'object') {
|
|
@@ -295,99 +309,302 @@ function deepSeal(obj) {
|
|
|
295
309
|
return obj;
|
|
296
310
|
}
|
|
297
311
|
|
|
298
|
-
|
|
299
|
-
function updateStore(dataSourceName, data, options = {}) {
|
|
300
|
-
if (isInitializing && !options.allowDuringInit) return;
|
|
312
|
+
const rawOf = (obj) => (typeof Alpine !== 'undefined' && Alpine.raw && obj ? Alpine.raw(obj) : obj);
|
|
301
313
|
|
|
302
|
-
|
|
303
|
-
|
|
314
|
+
const rowKey = (row) => (row && typeof row === 'object' && (typeof row.$id === 'string' || typeof row.$id === 'number'))
|
|
315
|
+
? row.$id
|
|
316
|
+
: null;
|
|
304
317
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
318
|
+
function ensureStoreShape(store) {
|
|
319
|
+
if (!store._v) store._v = {};
|
|
320
|
+
}
|
|
308
321
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
);
|
|
322
|
+
// Version bump: per-source (`$x` reads subscribe here), `all`, and the legacy
|
|
323
|
+
// global counter kept for external readers (status/datepicker/charts)
|
|
324
|
+
function touchSources(store, sources) {
|
|
325
|
+
ensureStoreShape(store);
|
|
326
|
+
const v = store._v;
|
|
327
|
+
for (const source of sources) v[source] = (v[source] || 0) + 1;
|
|
328
|
+
v.all = (v.all || 0) + 1;
|
|
329
|
+
allDirty = true;
|
|
330
|
+
store._dataVersion = (store._dataVersion || 0) + 1;
|
|
331
|
+
}
|
|
315
332
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
333
|
+
function touchSource(dataSourceName) {
|
|
334
|
+
const store = typeof Alpine !== 'undefined' ? Alpine.store('data') : null;
|
|
335
|
+
if (store) touchSources(store, [dataSourceName]);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Post-settle hammer: re-run every `$x` reader (Alpine scheduler swallow)
|
|
339
|
+
function bumpAllVersions() {
|
|
340
|
+
const store = typeof Alpine !== 'undefined' ? Alpine.store('data') : null;
|
|
341
|
+
if (!store) return;
|
|
342
|
+
ensureStoreShape(store);
|
|
343
|
+
const raw = rawOf(store);
|
|
344
|
+
const sources = new Set(Object.keys(raw._v || {}));
|
|
345
|
+
for (const key of Object.keys(raw)) {
|
|
346
|
+
if (!key.startsWith('_') && key !== 'all' && typeof raw[key] !== 'function') sources.add(key);
|
|
323
347
|
}
|
|
348
|
+
sources.delete('all');
|
|
349
|
+
touchSources(store, sources);
|
|
350
|
+
}
|
|
324
351
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
352
|
+
function sameScalarOrList(a, b) {
|
|
353
|
+
if (a === b) return true;
|
|
354
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
355
|
+
for (let i = 0; i < a.length; i++) {
|
|
356
|
+
const x = a[i];
|
|
357
|
+
if (x !== b[i] || (x !== null && typeof x === 'object')) return false;
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Merge fields onto an existing tracked row (property-grain); `$files` stays bound
|
|
363
|
+
function mergeRowFields(target, fields, dataSourceName) {
|
|
364
|
+
if (!target || !fields || typeof fields !== 'object') return;
|
|
365
|
+
const raw = rawOf(target);
|
|
366
|
+
for (const key of Object.keys(fields)) {
|
|
367
|
+
if (key === '$files') continue;
|
|
368
|
+
const value = fields[key];
|
|
369
|
+
if (sameScalarOrList(raw[key], value)) continue;
|
|
370
|
+
target[key] = createReactiveReferences(value, dataSourceName);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function attachMethods(array, dataSourceName) {
|
|
375
|
+
const loadDataSource = window.ManifestDataMain?.loadDataSource;
|
|
376
|
+
if (Array.isArray(array) && loadDataSource && window.ManifestDataProxies?.attachArrayMethods) {
|
|
377
|
+
window.ManifestDataProxies.attachArrayMethods(array, dataSourceName, loadDataSource);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Reactive source array, created on demand; raw map tracks the same identity
|
|
382
|
+
function ensureSourceArray(dataSourceName) {
|
|
383
|
+
const store = Alpine.store('data');
|
|
384
|
+
if (!store) return null;
|
|
385
|
+
const current = rawOf(store)[dataSourceName];
|
|
386
|
+
if (Array.isArray(current)) return store[dataSourceName];
|
|
387
|
+
if (current !== null && current !== undefined) return null;
|
|
388
|
+
store[dataSourceName] = [];
|
|
389
|
+
const created = rawOf(store)[dataSourceName];
|
|
390
|
+
rawDataStore.set(dataSourceName, created);
|
|
391
|
+
attachMethods(created, dataSourceName);
|
|
392
|
+
return store[dataSourceName];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Identity-preserving upsert by $id: existing rows are merged in place, new
|
|
396
|
+
// rows created; append keeps the array, replace swaps it only when membership
|
|
397
|
+
// or order changed
|
|
398
|
+
function upsertRows(store, dataSourceName, rows, mode) {
|
|
399
|
+
const raw = rawOf(store);
|
|
400
|
+
const curRaw = Array.isArray(raw[dataSourceName]) ? raw[dataSourceName] : null;
|
|
401
|
+
if (!curRaw) {
|
|
402
|
+
store[dataSourceName] = rows.map(row => createReactiveReferences(row, dataSourceName));
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const cur = store[dataSourceName];
|
|
406
|
+
const index = new Map();
|
|
407
|
+
curRaw.forEach((row, i) => { const id = rowKey(row); if (id !== null) index.set(id, i); });
|
|
408
|
+
const fresh = new Map();
|
|
409
|
+
const next = [];
|
|
410
|
+
const appended = [];
|
|
411
|
+
for (const row of rows) {
|
|
412
|
+
const id = rowKey(row);
|
|
413
|
+
const i = id !== null ? index.get(id) : undefined;
|
|
414
|
+
if (i !== undefined) {
|
|
415
|
+
mergeRowFields(cur[i], row, dataSourceName);
|
|
416
|
+
if (mode === 'replace') next.push(curRaw[i]);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (id !== null && fresh.has(id)) {
|
|
420
|
+
mergeRowFields(fresh.get(id), row, dataSourceName);
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
const created = createReactiveReferences(row, dataSourceName);
|
|
424
|
+
if (id !== null) fresh.set(id, created);
|
|
425
|
+
(mode === 'replace' ? next : appended).push(created);
|
|
426
|
+
}
|
|
427
|
+
if (mode === 'replace') {
|
|
428
|
+
if (next.length !== curRaw.length || next.some((row, i) => row !== curRaw[i])) store[dataSourceName] = next;
|
|
429
|
+
} else if (appended.length) {
|
|
430
|
+
cur.push(...appended);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function removeRows(store, dataSourceName, ids) {
|
|
435
|
+
const curRaw = rawOf(store)[dataSourceName];
|
|
436
|
+
if (!Array.isArray(curRaw) || !ids?.length) return false;
|
|
437
|
+
const set = new Set(ids);
|
|
438
|
+
const cur = store[dataSourceName];
|
|
439
|
+
let removed = false;
|
|
440
|
+
for (let i = curRaw.length - 1; i >= 0; i--) {
|
|
441
|
+
const id = rowKey(curRaw[i]);
|
|
442
|
+
if (id !== null && set.has(id)) { cur.splice(i, 1); removed = true; }
|
|
443
|
+
}
|
|
444
|
+
return removed;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Synchronous write of data + state for one source (no version bump); returns the new state
|
|
448
|
+
function writeSource(dataSourceName, data, options = {}) {
|
|
449
|
+
const store = Alpine.store('data');
|
|
450
|
+
if (!store) return null;
|
|
451
|
+
ensureStoreShape(store);
|
|
452
|
+
const mode = options.mode === 'append' ? 'append' : 'replace';
|
|
331
453
|
|
|
332
|
-
|
|
454
|
+
if (data === null || data === undefined) {
|
|
455
|
+
if (mode === 'replace') {
|
|
456
|
+
store[dataSourceName] = data;
|
|
457
|
+
rawDataStore.set(dataSourceName, data);
|
|
458
|
+
}
|
|
459
|
+
} else if (Array.isArray(data)) {
|
|
460
|
+
upsertRows(store, dataSourceName, data, mode);
|
|
461
|
+
const arr = rawOf(store)[dataSourceName];
|
|
462
|
+
rawDataStore.set(dataSourceName, arr);
|
|
463
|
+
attachMethods(arr, dataSourceName);
|
|
464
|
+
} else {
|
|
465
|
+
store[dataSourceName] = data;
|
|
466
|
+
rawDataStore.set(dataSourceName, data);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const currentState = store[`_${dataSourceName}_state`] || { loading: false, error: null, ready: false };
|
|
333
470
|
const newState = {
|
|
334
471
|
loading: options.loading !== undefined ? options.loading : currentState.loading,
|
|
335
472
|
error: options.error !== undefined ? options.error : currentState.error,
|
|
336
473
|
ready: options.ready !== undefined ? options.ready : (data !== null && data !== undefined),
|
|
337
474
|
errorTime: options.error !== undefined && options.error !== null ? Date.now() : (currentState.errorTime || null)
|
|
338
475
|
};
|
|
476
|
+
store[`_${dataSourceName}_state`] = newState;
|
|
477
|
+
store._initialized = true;
|
|
478
|
+
store._ready = true;
|
|
339
479
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// Bump version so any effect that read the store re-runs (fixes bindings stuck on loading fallback)
|
|
348
|
-
const dataVersion = (store._dataVersion || 0) + 1;
|
|
349
|
-
const updatedStore = {
|
|
350
|
-
...store,
|
|
351
|
-
[dataSourceName]: reactiveData, // Store actual data in Alpine store (like backup)
|
|
352
|
-
[`_${dataSourceName}_state`]: newState, // Store state for this data source
|
|
353
|
-
all,
|
|
354
|
-
_initialized: true,
|
|
355
|
-
_ready: true, // Mark as ready when first data source is loaded
|
|
356
|
-
_dataVersion: dataVersion
|
|
357
|
-
};
|
|
480
|
+
const proxies = window.ManifestDataProxies;
|
|
481
|
+
proxies?.clearAccessCache?.(dataSourceName);
|
|
482
|
+
proxies?.clearArrayProxyCacheForDataSource?.(dataSourceName);
|
|
483
|
+
proxies?.clearRouteProxyCacheForDataSource?.(dataSourceName);
|
|
484
|
+
proxies?.clearNestedProxyCacheForDataSource?.(dataSourceName);
|
|
485
|
+
return newState;
|
|
486
|
+
}
|
|
358
487
|
|
|
359
|
-
|
|
488
|
+
// Synchronous replace: local writes ($register, init preload, state-only updates)
|
|
489
|
+
function updateStore(dataSourceName, data, options = {}) {
|
|
490
|
+
if (isInitializing && !options.allowDuringInit) return;
|
|
491
|
+
const state = writeSource(dataSourceName, data, { ...options, mode: 'replace' });
|
|
492
|
+
if (!state) return;
|
|
493
|
+
touchSource(dataSourceName);
|
|
494
|
+
if (!state.loading) checkAndDispatchRenderReady();
|
|
495
|
+
}
|
|
360
496
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
497
|
+
// Network landing (page load, paged append, realtime batch): buffered, applied
|
|
498
|
+
// with every other landing of the same frame in ONE flush. Resolves once visible.
|
|
499
|
+
function landRows(dataSourceName, rows, options = {}) {
|
|
500
|
+
return queueLanding({ source: dataSourceName, rows, options: { mode: 'replace', ...options } });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function landRemove(dataSourceName, ids, options = {}) {
|
|
504
|
+
return queueLanding({ source: dataSourceName, remove: Array.isArray(ids) ? ids : [ids], options });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function queueLanding(op) {
|
|
508
|
+
return new Promise(resolve => {
|
|
509
|
+
pendingLandings.push(op);
|
|
510
|
+
landingResolvers.push(resolve);
|
|
511
|
+
scheduleLandingFlush();
|
|
512
|
+
});
|
|
513
|
+
}
|
|
366
514
|
|
|
367
|
-
|
|
368
|
-
if (
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
515
|
+
function scheduleLandingFlush() {
|
|
516
|
+
if (landingFrame !== null || landingTimer !== null) return;
|
|
517
|
+
const hidden = typeof document !== 'undefined' && document.hidden;
|
|
518
|
+
if (!hidden && typeof requestAnimationFrame === 'function') {
|
|
519
|
+
landingFrame = requestAnimationFrame(flushLandings);
|
|
520
|
+
landingTimer = setTimeout(flushLandings, LANDING_FALLBACK_MS);
|
|
521
|
+
} else {
|
|
522
|
+
landingTimer = setTimeout(flushLandings, 0);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Local write while a flush is pending: replayed on top of that flush
|
|
527
|
+
function noteLocalWrite(dataSourceName, id, note) {
|
|
528
|
+
if (!pendingLandings.length || id === null || id === undefined) return;
|
|
529
|
+
let byId = pendingLocal.get(dataSourceName);
|
|
530
|
+
if (!byId) pendingLocal.set(dataSourceName, byId = new Map());
|
|
531
|
+
if (note.removed) { byId.set(id, { removed: true }); return; }
|
|
532
|
+
const prev = byId.get(id);
|
|
533
|
+
const base = prev && !prev.removed ? prev.patch : {};
|
|
534
|
+
byId.set(id, { removed: false, patch: { ...base, ...note.patch } });
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function flushLandings() {
|
|
538
|
+
if (landingFrame !== null) { cancelAnimationFrame(landingFrame); landingFrame = null; }
|
|
539
|
+
if (landingTimer !== null) { clearTimeout(landingTimer); landingTimer = null; }
|
|
540
|
+
const ops = pendingLandings.splice(0);
|
|
541
|
+
const resolvers = landingResolvers.splice(0);
|
|
542
|
+
const local = new Map(pendingLocal);
|
|
543
|
+
pendingLocal.clear();
|
|
544
|
+
if (!ops.length) return;
|
|
545
|
+
|
|
546
|
+
const store = typeof Alpine !== 'undefined' ? Alpine.store('data') : null;
|
|
547
|
+
const touched = new Set();
|
|
548
|
+
let settled = false;
|
|
549
|
+
if (store) {
|
|
550
|
+
for (const op of ops) {
|
|
551
|
+
try {
|
|
552
|
+
if (op.remove) {
|
|
553
|
+
if (removeRows(store, op.source, op.remove)) touched.add(op.source);
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
const state = writeSource(op.source, op.rows, op.options);
|
|
557
|
+
if (!state) continue;
|
|
558
|
+
touched.add(op.source);
|
|
559
|
+
// Only load completions (explicit loading state) feed render-ready; realtime upserts never do
|
|
560
|
+
if (op.options.loading !== undefined && !state.loading) settled = true;
|
|
561
|
+
} catch (error) {
|
|
562
|
+
console.error(`[Manifest Data] Landing failed for "${op.source}":`, error);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
for (const [source, byId] of local) {
|
|
566
|
+
for (const [id, note] of byId) {
|
|
567
|
+
if (note.removed) {
|
|
568
|
+
if (removeRows(store, source, [id])) touched.add(source);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const curRaw = rawOf(store)[source];
|
|
572
|
+
const i = Array.isArray(curRaw) ? curRaw.findIndex(row => rowKey(row) === id) : -1;
|
|
573
|
+
if (i !== -1) { mergeRowFields(store[source][i], note.patch, source); touched.add(source); }
|
|
574
|
+
}
|
|
372
575
|
}
|
|
576
|
+
if (touched.size) touchSources(store, touched);
|
|
373
577
|
}
|
|
578
|
+
resolvers.forEach(resolve => resolve());
|
|
579
|
+
if (settled) checkAndDispatchRenderReady();
|
|
580
|
+
}
|
|
374
581
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
582
|
+
// Cross-source `all`: built on first read after a change, versioned by _v.all
|
|
583
|
+
function getAll() {
|
|
584
|
+
const store = typeof Alpine !== 'undefined' ? Alpine.store('data') : null;
|
|
585
|
+
if (!store) return [];
|
|
586
|
+
ensureStoreShape(store);
|
|
587
|
+
void store._v.all;
|
|
588
|
+
if (allDirty || !allCache) {
|
|
589
|
+
const raw = rawOf(store);
|
|
590
|
+
const wrap = Alpine.reactive ? (item => Alpine.reactive(item)) : (item => item);
|
|
591
|
+
const out = [];
|
|
592
|
+
for (const key of Object.keys(raw)) {
|
|
593
|
+
if (key.startsWith('_') || key === 'all') continue;
|
|
594
|
+
const value = raw[key];
|
|
595
|
+
if (Array.isArray(value)) {
|
|
596
|
+
for (const item of value) {
|
|
597
|
+
if (item !== null && item !== undefined) out.push(typeof item === 'object' ? wrap(item) : item);
|
|
598
|
+
}
|
|
599
|
+
} else if (value && typeof value === 'object') {
|
|
600
|
+
out.push(wrap(value));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
allCache = out;
|
|
604
|
+
allDirty = false;
|
|
605
|
+
attachMethods(out, 'all');
|
|
390
606
|
}
|
|
607
|
+
return allCache;
|
|
391
608
|
}
|
|
392
609
|
|
|
393
610
|
// Get raw data from our non-reactive store
|
|
@@ -649,10 +866,10 @@ function createReactiveReferences(data, dataSourceName = null) {
|
|
|
649
866
|
// Initialize store
|
|
650
867
|
function initializeStore() {
|
|
651
868
|
const initialStore = {
|
|
652
|
-
all: [], // Global content array for cross-dataSource access
|
|
653
869
|
_initialized: false,
|
|
654
870
|
_ready: false, // Flag to indicate when data is ready for Alpine evaluation
|
|
655
|
-
|
|
871
|
+
_v: {}, // Per-source versions: `$x.<source>` reads subscribe to _v[source]; _v.all for `$x.all`
|
|
872
|
+
_dataVersion: 0, // Legacy global counter, bumped once per flush for external readers
|
|
656
873
|
_currentUrl: window.location.pathname,
|
|
657
874
|
// Operation-specific loading states (for UI reactivity)
|
|
658
875
|
// Format: { dataSourceName: { entryId: true } }
|
|
@@ -675,6 +892,10 @@ function initializeStore() {
|
|
|
675
892
|
return isUploadingFile(dataSourceName, entryId, fileId);
|
|
676
893
|
}
|
|
677
894
|
};
|
|
895
|
+
// Lazy cross-source array (non-enumerable so store spreads/key scans skip it)
|
|
896
|
+
Object.defineProperty(initialStore, 'all', { enumerable: false, configurable: true, get: getAll });
|
|
897
|
+
allCache = null;
|
|
898
|
+
allDirty = true;
|
|
678
899
|
Alpine.store('data', initialStore);
|
|
679
900
|
}
|
|
680
901
|
|
|
@@ -754,11 +975,10 @@ function setupTeamChangeListener() {
|
|
|
754
975
|
// Remove team-scoped data from store
|
|
755
976
|
const store = Alpine.store('data');
|
|
756
977
|
if (store) {
|
|
757
|
-
const newStore = { ...store };
|
|
758
978
|
teamScopedDataSources.forEach(dataSourceName => {
|
|
759
|
-
delete
|
|
979
|
+
delete store[dataSourceName];
|
|
760
980
|
});
|
|
761
|
-
|
|
981
|
+
touchSources(store, teamScopedDataSources);
|
|
762
982
|
}
|
|
763
983
|
|
|
764
984
|
// Clear proxy cache for these data sources
|
|
@@ -781,16 +1001,6 @@ function setupTeamChangeListener() {
|
|
|
781
1001
|
}
|
|
782
1002
|
})).then(() => {
|
|
783
1003
|
});
|
|
784
|
-
} else {
|
|
785
|
-
// Fallback: Delete from store to force reload on next access
|
|
786
|
-
const store = Alpine.store('data');
|
|
787
|
-
if (store) {
|
|
788
|
-
const newStore = { ...store };
|
|
789
|
-
teamScopedDataSources.forEach(dataSourceName => {
|
|
790
|
-
delete newStore[dataSourceName];
|
|
791
|
-
});
|
|
792
|
-
Alpine.store('data', newStore);
|
|
793
|
-
}
|
|
794
1004
|
}
|
|
795
1005
|
}
|
|
796
1006
|
} catch (error) {
|
|
@@ -863,10 +1073,7 @@ function checkAndDispatchRenderReady() {
|
|
|
863
1073
|
// never re-run. One post-settle bump on a fresh task re-runs
|
|
864
1074
|
// anything dropped.
|
|
865
1075
|
setTimeout(() => {
|
|
866
|
-
try {
|
|
867
|
-
const s = Alpine.store('data');
|
|
868
|
-
if (s) Alpine.store('data', { ...s, _dataVersion: (s._dataVersion || 0) + 1 });
|
|
869
|
-
} catch (_) { /* no-op */ }
|
|
1076
|
+
try { bumpAllVersions(); } catch (_) { /* no-op */ }
|
|
870
1077
|
}, 50);
|
|
871
1078
|
} catch {
|
|
872
1079
|
// Silently fail — the render script has its own timeout fallback
|
|
@@ -881,12 +1088,7 @@ function setupLocaleChangeListener() {
|
|
|
881
1088
|
|
|
882
1089
|
// Set loading state to prevent flicker
|
|
883
1090
|
const store = Alpine.store('data');
|
|
884
|
-
if (store)
|
|
885
|
-
Alpine.store('data', {
|
|
886
|
-
...store,
|
|
887
|
-
_localeChanging: true
|
|
888
|
-
});
|
|
889
|
-
}
|
|
1091
|
+
if (store) store._localeChanging = true;
|
|
890
1092
|
|
|
891
1093
|
try {
|
|
892
1094
|
// Get manifest to identify localized data sources
|
|
@@ -979,23 +1181,13 @@ function setupLocaleChangeListener() {
|
|
|
979
1181
|
|
|
980
1182
|
// Remove localized data from store so bindings see missing data and re-run
|
|
981
1183
|
const store = Alpine.store('data');
|
|
982
|
-
if (store
|
|
983
|
-
const filteredAll = store.all.filter(item =>
|
|
984
|
-
!localizedDataSources.includes(item.contentType)
|
|
985
|
-
);
|
|
986
|
-
|
|
987
|
-
const newStore = { ...store, all: filteredAll };
|
|
1184
|
+
if (store) {
|
|
988
1185
|
localizedDataSources.forEach(dataSourceName => {
|
|
989
|
-
delete
|
|
990
|
-
delete
|
|
991
|
-
});
|
|
992
|
-
|
|
993
|
-
const dataVersion = (store._dataVersion || 0) + 1;
|
|
994
|
-
Alpine.store('data', {
|
|
995
|
-
...newStore,
|
|
996
|
-
_localeChanging: false,
|
|
997
|
-
_dataVersion: dataVersion
|
|
1186
|
+
delete store[dataSourceName];
|
|
1187
|
+
delete store[`_${dataSourceName}_state`];
|
|
998
1188
|
});
|
|
1189
|
+
store._localeChanging = false;
|
|
1190
|
+
touchSources(store, localizedDataSources);
|
|
999
1191
|
}
|
|
1000
1192
|
|
|
1001
1193
|
// Proactively reload localized sources with the new locale so the UI updates.
|
|
@@ -1016,11 +1208,17 @@ function setupLocaleChangeListener() {
|
|
|
1016
1208
|
// Fallback to full reload if something goes wrong
|
|
1017
1209
|
dataSourceCache.clear();
|
|
1018
1210
|
loadingPromises.clear();
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1211
|
+
rawDataStore.clear();
|
|
1212
|
+
const store = Alpine.store('data');
|
|
1213
|
+
if (store) {
|
|
1214
|
+
const raw = rawOf(store);
|
|
1215
|
+
for (const key of Object.keys(raw)) {
|
|
1216
|
+
if (!key.startsWith('_') && typeof raw[key] !== 'function') delete store[key];
|
|
1217
|
+
}
|
|
1218
|
+
store._initialized = true;
|
|
1219
|
+
store._localeChanging = false;
|
|
1220
|
+
bumpAllVersions();
|
|
1221
|
+
}
|
|
1024
1222
|
}
|
|
1025
1223
|
});
|
|
1026
1224
|
}
|
|
@@ -1043,12 +1241,6 @@ function setCreatingEntry(dataSourceName, entryId) {
|
|
|
1043
1241
|
...store._creatingEntry[dataSourceName],
|
|
1044
1242
|
[entryId]: true
|
|
1045
1243
|
};
|
|
1046
|
-
|
|
1047
|
-
// Update store to trigger reactivity
|
|
1048
|
-
Alpine.store('data', {
|
|
1049
|
-
...store,
|
|
1050
|
-
_creatingEntry: { ...store._creatingEntry }
|
|
1051
|
-
});
|
|
1052
1244
|
}
|
|
1053
1245
|
|
|
1054
1246
|
function clearCreatingEntry(dataSourceName, entryId) {
|
|
@@ -1058,12 +1250,6 @@ function clearCreatingEntry(dataSourceName, entryId) {
|
|
|
1058
1250
|
// Create new object without this entryId
|
|
1059
1251
|
const { [entryId]: removed, ...rest } = store._creatingEntry[dataSourceName];
|
|
1060
1252
|
store._creatingEntry[dataSourceName] = rest;
|
|
1061
|
-
|
|
1062
|
-
// Update store to trigger reactivity
|
|
1063
|
-
Alpine.store('data', {
|
|
1064
|
-
...store,
|
|
1065
|
-
_creatingEntry: { ...store._creatingEntry }
|
|
1066
|
-
});
|
|
1067
1253
|
}
|
|
1068
1254
|
|
|
1069
1255
|
function setUpdatingEntry(dataSourceName, entryId) {
|
|
@@ -1082,12 +1268,6 @@ function setUpdatingEntry(dataSourceName, entryId) {
|
|
|
1082
1268
|
...store._updatingEntry[dataSourceName],
|
|
1083
1269
|
[entryId]: true
|
|
1084
1270
|
};
|
|
1085
|
-
|
|
1086
|
-
// Update store to trigger reactivity
|
|
1087
|
-
Alpine.store('data', {
|
|
1088
|
-
...store,
|
|
1089
|
-
_updatingEntry: { ...store._updatingEntry }
|
|
1090
|
-
});
|
|
1091
1271
|
}
|
|
1092
1272
|
|
|
1093
1273
|
function clearUpdatingEntry(dataSourceName, entryId) {
|
|
@@ -1097,12 +1277,6 @@ function clearUpdatingEntry(dataSourceName, entryId) {
|
|
|
1097
1277
|
// Create new object without this entryId
|
|
1098
1278
|
const { [entryId]: removed, ...rest } = store._updatingEntry[dataSourceName];
|
|
1099
1279
|
store._updatingEntry[dataSourceName] = rest;
|
|
1100
|
-
|
|
1101
|
-
// Update store to trigger reactivity
|
|
1102
|
-
Alpine.store('data', {
|
|
1103
|
-
...store,
|
|
1104
|
-
_updatingEntry: { ...store._updatingEntry }
|
|
1105
|
-
});
|
|
1106
1280
|
}
|
|
1107
1281
|
|
|
1108
1282
|
function setDeletingEntry(dataSourceName, entryId) {
|
|
@@ -1121,12 +1295,6 @@ function setDeletingEntry(dataSourceName, entryId) {
|
|
|
1121
1295
|
...store._deletingEntry[dataSourceName],
|
|
1122
1296
|
[entryId]: true
|
|
1123
1297
|
};
|
|
1124
|
-
|
|
1125
|
-
// Update store to trigger reactivity
|
|
1126
|
-
Alpine.store('data', {
|
|
1127
|
-
...store,
|
|
1128
|
-
_deletingEntry: { ...store._deletingEntry }
|
|
1129
|
-
});
|
|
1130
1298
|
}
|
|
1131
1299
|
|
|
1132
1300
|
function clearDeletingEntry(dataSourceName, entryId) {
|
|
@@ -1136,12 +1304,6 @@ function clearDeletingEntry(dataSourceName, entryId) {
|
|
|
1136
1304
|
// Create new object without this entryId
|
|
1137
1305
|
const { [entryId]: removed, ...rest } = store._deletingEntry[dataSourceName];
|
|
1138
1306
|
store._deletingEntry[dataSourceName] = rest;
|
|
1139
|
-
|
|
1140
|
-
// Update store to trigger reactivity
|
|
1141
|
-
Alpine.store('data', {
|
|
1142
|
-
...store,
|
|
1143
|
-
_deletingEntry: { ...store._deletingEntry }
|
|
1144
|
-
});
|
|
1145
1307
|
}
|
|
1146
1308
|
|
|
1147
1309
|
function setUploadingFile(dataSourceName, entryId, fileId) {
|
|
@@ -1163,12 +1325,6 @@ function setUploadingFile(dataSourceName, entryId, fileId) {
|
|
|
1163
1325
|
...store._uploadingFile[dataSourceName][entryId],
|
|
1164
1326
|
[fileId]: true
|
|
1165
1327
|
};
|
|
1166
|
-
|
|
1167
|
-
// Update store to trigger reactivity
|
|
1168
|
-
Alpine.store('data', {
|
|
1169
|
-
...store,
|
|
1170
|
-
_uploadingFile: { ...store._uploadingFile }
|
|
1171
|
-
});
|
|
1172
1328
|
}
|
|
1173
1329
|
|
|
1174
1330
|
function clearUploadingFile(dataSourceName, entryId, fileId) {
|
|
@@ -1184,12 +1340,6 @@ function clearUploadingFile(dataSourceName, entryId, fileId) {
|
|
|
1184
1340
|
const { [entryId]: removedEntry, ...restEntries } = store._uploadingFile[dataSourceName];
|
|
1185
1341
|
store._uploadingFile[dataSourceName] = restEntries;
|
|
1186
1342
|
}
|
|
1187
|
-
|
|
1188
|
-
// Update store to trigger reactivity
|
|
1189
|
-
Alpine.store('data', {
|
|
1190
|
-
...store,
|
|
1191
|
-
_uploadingFile: { ...store._uploadingFile }
|
|
1192
|
-
});
|
|
1193
1343
|
}
|
|
1194
1344
|
|
|
1195
1345
|
// Helper methods to check operation-specific loading states
|
|
@@ -1231,6 +1381,17 @@ window.ManifestDataStore = {
|
|
|
1231
1381
|
setIsInitializing: (value) => { isInitializing = value; },
|
|
1232
1382
|
setInitializationComplete: (value) => { initializationComplete = value; },
|
|
1233
1383
|
updateStore,
|
|
1384
|
+
// Landing model (PERF-PRIMITIVES-DESIGN.md §5)
|
|
1385
|
+
landRows,
|
|
1386
|
+
landRemove,
|
|
1387
|
+
flushLandings,
|
|
1388
|
+
noteLocalWrite,
|
|
1389
|
+
touchSource,
|
|
1390
|
+
bumpAllVersions,
|
|
1391
|
+
mergeRowFields,
|
|
1392
|
+
ensureSourceArray,
|
|
1393
|
+
rawOf,
|
|
1394
|
+
getAll,
|
|
1234
1395
|
getRawData,
|
|
1235
1396
|
createReactiveReferences,
|
|
1236
1397
|
initializeStore,
|
|
@@ -1864,78 +2025,40 @@ function generateMutationId() {
|
|
|
1864
2025
|
return `mutation_${Date.now()}_${++mutationIdCounter}`;
|
|
1865
2026
|
}
|
|
1866
2027
|
|
|
2028
|
+
// Local writes are synchronous (read-your-writes) and identity-preserving;
|
|
2029
|
+
// network landings go through ManifestDataStore.landRows instead.
|
|
2030
|
+
function clearSourceCaches(dataSourceName) {
|
|
2031
|
+
const proxies = window.ManifestDataProxies;
|
|
2032
|
+
proxies?.clearAccessCache?.(dataSourceName);
|
|
2033
|
+
proxies?.clearArrayProxyCacheForDataSource?.(dataSourceName);
|
|
2034
|
+
proxies?.clearRouteProxyCacheForDataSource?.(dataSourceName);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
function findRawIndex(dataSourceName, entryId) {
|
|
2038
|
+
const store = Alpine.store('data');
|
|
2039
|
+
const raw = window.ManifestDataStore?.rawOf?.(store) || store;
|
|
2040
|
+
const arr = raw?.[dataSourceName];
|
|
2041
|
+
if (!Array.isArray(arr)) return { store, arr: null, index: -1 };
|
|
2042
|
+
return { store, arr, index: arr.findIndex(entry => entry && entry.$id === entryId) };
|
|
2043
|
+
}
|
|
2044
|
+
|
|
1867
2045
|
// Update a single entry in the store (scoped update)
|
|
1868
2046
|
function updateEntryInStore(dataSourceName, entryId, updates, options = {}) {
|
|
1869
2047
|
if (typeof Alpine === 'undefined' || !Alpine.store) {
|
|
1870
2048
|
return false;
|
|
1871
2049
|
}
|
|
1872
2050
|
|
|
1873
|
-
const store =
|
|
1874
|
-
if (!store || !
|
|
1875
|
-
return false;
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
const currentArray = store[dataSourceName];
|
|
1879
|
-
const index = currentArray.findIndex(entry => entry.$id === entryId);
|
|
1880
|
-
|
|
1881
|
-
if (index === -1) {
|
|
2051
|
+
const { store, arr, index } = findRawIndex(dataSourceName, entryId);
|
|
2052
|
+
if (!store || !arr || index === -1) {
|
|
1882
2053
|
return false;
|
|
1883
2054
|
}
|
|
1884
2055
|
|
|
1885
|
-
//
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
// Get stack trace to see who's calling this
|
|
1892
|
-
const stack = new Error().stack;
|
|
1893
|
-
const caller = stack?.split('\n')[2]?.trim() || 'unknown';
|
|
1894
|
-
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
// Create new array with updated entry
|
|
1898
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
1899
|
-
const newArray = currentArray.map((entry, i) => {
|
|
1900
|
-
if (i === index) {
|
|
1901
|
-
// Merge updates into entry, creating new object reference
|
|
1902
|
-
const updatedEntry = createReactiveReferences
|
|
1903
|
-
? createReactiveReferences({ ...entry, ...updates }, dataSourceName)
|
|
1904
|
-
: { ...entry, ...updates };
|
|
1905
|
-
return updatedEntry;
|
|
1906
|
-
}
|
|
1907
|
-
return entry;
|
|
1908
|
-
});
|
|
1909
|
-
|
|
1910
|
-
// Create reactive references for entire array
|
|
1911
|
-
const reactiveArray = createReactiveReferences
|
|
1912
|
-
? createReactiveReferences(newArray, dataSourceName)
|
|
1913
|
-
: newArray;
|
|
1914
|
-
|
|
1915
|
-
// Update store
|
|
1916
|
-
Alpine.store('data', {
|
|
1917
|
-
...store,
|
|
1918
|
-
[dataSourceName]: reactiveArray
|
|
1919
|
-
});
|
|
1920
|
-
|
|
1921
|
-
// Attach methods to new array reference
|
|
1922
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
1923
|
-
const loadDataSource = window.ManifestDataMain?.loadDataSource;
|
|
1924
|
-
if (loadDataSource) {
|
|
1925
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveArray, dataSourceName, loadDataSource);
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
// Clear caches
|
|
1930
|
-
if (window.ManifestDataProxies?.clearAccessCache) {
|
|
1931
|
-
window.ManifestDataProxies.clearAccessCache(dataSourceName);
|
|
1932
|
-
}
|
|
1933
|
-
if (window.ManifestDataProxies?.clearArrayProxyCacheForDataSource) {
|
|
1934
|
-
window.ManifestDataProxies.clearArrayProxyCacheForDataSource(dataSourceName);
|
|
1935
|
-
}
|
|
1936
|
-
if (window.ManifestDataProxies?.clearRouteProxyCacheForDataSource) {
|
|
1937
|
-
window.ManifestDataProxies.clearRouteProxyCacheForDataSource(dataSourceName);
|
|
1938
|
-
}
|
|
2056
|
+
// Local write: merge onto the tracked row, sync
|
|
2057
|
+
const ds = window.ManifestDataStore;
|
|
2058
|
+
ds.mergeRowFields(store[dataSourceName][index], updates, dataSourceName);
|
|
2059
|
+
ds.noteLocalWrite(dataSourceName, entryId, { patch: updates });
|
|
2060
|
+
ds.touchSource(dataSourceName);
|
|
2061
|
+
clearSourceCaches(dataSourceName);
|
|
1939
2062
|
|
|
1940
2063
|
// Dispatch mutation event
|
|
1941
2064
|
window.dispatchEvent(new CustomEvent('manifest:data-mutated', {
|
|
@@ -1956,53 +2079,17 @@ function addEntryToStore(dataSourceName, entry, options = {}) {
|
|
|
1956
2079
|
return false;
|
|
1957
2080
|
}
|
|
1958
2081
|
|
|
1959
|
-
const
|
|
1960
|
-
|
|
2082
|
+
const ds = window.ManifestDataStore;
|
|
2083
|
+
const target = ds?.ensureSourceArray?.(dataSourceName);
|
|
2084
|
+
if (!target) {
|
|
1961
2085
|
return false;
|
|
1962
2086
|
}
|
|
1963
2087
|
|
|
1964
|
-
//
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
// Create reactive references for new entry
|
|
1971
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
1972
|
-
const newEntry = createReactiveReferences
|
|
1973
|
-
? createReactiveReferences(entry, dataSourceName)
|
|
1974
|
-
: entry;
|
|
1975
|
-
|
|
1976
|
-
// Create new array with new entry
|
|
1977
|
-
const newArray = [...currentArray, newEntry];
|
|
1978
|
-
const reactiveArray = createReactiveReferences
|
|
1979
|
-
? createReactiveReferences(newArray, dataSourceName)
|
|
1980
|
-
: newArray;
|
|
1981
|
-
|
|
1982
|
-
// Update store
|
|
1983
|
-
Alpine.store('data', {
|
|
1984
|
-
...store,
|
|
1985
|
-
[dataSourceName]: reactiveArray
|
|
1986
|
-
});
|
|
1987
|
-
|
|
1988
|
-
// Attach methods to new array reference
|
|
1989
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
1990
|
-
const loadDataSource = window.ManifestDataMain?.loadDataSource;
|
|
1991
|
-
if (loadDataSource) {
|
|
1992
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveArray, dataSourceName, loadDataSource);
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
// Clear caches
|
|
1997
|
-
if (window.ManifestDataProxies?.clearAccessCache) {
|
|
1998
|
-
window.ManifestDataProxies.clearAccessCache(dataSourceName);
|
|
1999
|
-
}
|
|
2000
|
-
if (window.ManifestDataProxies?.clearArrayProxyCacheForDataSource) {
|
|
2001
|
-
window.ManifestDataProxies.clearArrayProxyCacheForDataSource(dataSourceName);
|
|
2002
|
-
}
|
|
2003
|
-
if (window.ManifestDataProxies?.clearRouteProxyCacheForDataSource) {
|
|
2004
|
-
window.ManifestDataProxies.clearRouteProxyCacheForDataSource(dataSourceName);
|
|
2005
|
-
}
|
|
2088
|
+
// Local write: push onto the tracked array, sync
|
|
2089
|
+
target.push(ds.createReactiveReferences(entry, dataSourceName));
|
|
2090
|
+
ds.noteLocalWrite(dataSourceName, entry?.$id, { patch: entry });
|
|
2091
|
+
ds.touchSource(dataSourceName);
|
|
2092
|
+
clearSourceCaches(dataSourceName);
|
|
2006
2093
|
|
|
2007
2094
|
// Dispatch mutation event
|
|
2008
2095
|
window.dispatchEvent(new CustomEvent('manifest:data-mutated', {
|
|
@@ -2023,49 +2110,20 @@ function removeEntryFromStore(dataSourceName, entryId, options = {}) {
|
|
|
2023
2110
|
return false;
|
|
2024
2111
|
}
|
|
2025
2112
|
|
|
2026
|
-
const store =
|
|
2027
|
-
if (!store || !
|
|
2028
|
-
return false;
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
const currentArray = store[dataSourceName];
|
|
2032
|
-
const index = currentArray.findIndex(entry => entry.$id === entryId);
|
|
2033
|
-
|
|
2034
|
-
if (index === -1) {
|
|
2113
|
+
const { store, arr, index } = findRawIndex(dataSourceName, entryId);
|
|
2114
|
+
if (!store || !arr || index === -1) {
|
|
2035
2115
|
return false;
|
|
2036
2116
|
}
|
|
2037
2117
|
|
|
2038
2118
|
// Store original entry for rollback
|
|
2039
|
-
const originalEntry =
|
|
2040
|
-
|
|
2041
|
-
// Create new array without entry
|
|
2042
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
2043
|
-
const newArray = currentArray.filter((entry, i) => i !== index);
|
|
2044
|
-
const reactiveArray = createReactiveReferences
|
|
2045
|
-
? createReactiveReferences(newArray, dataSourceName)
|
|
2046
|
-
: newArray;
|
|
2047
|
-
|
|
2048
|
-
// Update store
|
|
2049
|
-
Alpine.store('data', {
|
|
2050
|
-
...store,
|
|
2051
|
-
[dataSourceName]: reactiveArray
|
|
2052
|
-
});
|
|
2053
|
-
|
|
2054
|
-
// Attach methods to new array reference
|
|
2055
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
2056
|
-
const loadDataSource = window.ManifestDataMain?.loadDataSource;
|
|
2057
|
-
if (loadDataSource) {
|
|
2058
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveArray, dataSourceName, loadDataSource);
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2119
|
+
const originalEntry = arr[index];
|
|
2061
2120
|
|
|
2062
|
-
//
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
}
|
|
2121
|
+
// Local write: splice the tracked array, sync
|
|
2122
|
+
const ds = window.ManifestDataStore;
|
|
2123
|
+
store[dataSourceName].splice(index, 1);
|
|
2124
|
+
ds.noteLocalWrite(dataSourceName, entryId, { removed: true });
|
|
2125
|
+
ds.touchSource(dataSourceName);
|
|
2126
|
+
clearSourceCaches(dataSourceName);
|
|
2069
2127
|
|
|
2070
2128
|
// Dispatch mutation event
|
|
2071
2129
|
window.dispatchEvent(new CustomEvent('manifest:data-mutated', {
|
|
@@ -2157,10 +2215,11 @@ async function executeMutation(mutationConfig) {
|
|
|
2157
2215
|
addEntryToStore(dataSourceName, optimisticData, options);
|
|
2158
2216
|
originalData = null; // Nothing to rollback for create
|
|
2159
2217
|
} else if (type === 'update') {
|
|
2160
|
-
//
|
|
2218
|
+
// Snapshot for rollback (the row itself is mutated in place)
|
|
2161
2219
|
const store = Alpine.store('data');
|
|
2162
2220
|
if (store && store[dataSourceName] && Array.isArray(store[dataSourceName])) {
|
|
2163
|
-
|
|
2221
|
+
const found = store[dataSourceName].find(e => e.$id === entryId);
|
|
2222
|
+
originalData = found ? { ...found } : null;
|
|
2164
2223
|
}
|
|
2165
2224
|
|
|
2166
2225
|
// Set updating state
|
|
@@ -2174,10 +2233,11 @@ async function executeMutation(mutationConfig) {
|
|
|
2174
2233
|
updateEntryInStore(dataSourceName, entryId, data, options);
|
|
2175
2234
|
}
|
|
2176
2235
|
} else if (type === 'delete') {
|
|
2177
|
-
//
|
|
2236
|
+
// Snapshot for rollback
|
|
2178
2237
|
const store = Alpine.store('data');
|
|
2179
2238
|
if (store && store[dataSourceName] && Array.isArray(store[dataSourceName])) {
|
|
2180
|
-
|
|
2239
|
+
const found = store[dataSourceName].find(e => e.$id === entryId);
|
|
2240
|
+
originalData = found ? { ...found } : null;
|
|
2181
2241
|
}
|
|
2182
2242
|
|
|
2183
2243
|
// Set deleting state
|
|
@@ -2207,47 +2267,14 @@ async function executeMutation(mutationConfig) {
|
|
|
2207
2267
|
// Step 4: Background sync - update with server response
|
|
2208
2268
|
if (result && result.$id) {
|
|
2209
2269
|
if (type === 'create') {
|
|
2210
|
-
//
|
|
2211
|
-
const store =
|
|
2212
|
-
if (store &&
|
|
2213
|
-
const index = store[dataSourceName].findIndex(e => e.$id === optimisticData.$id);
|
|
2270
|
+
// Ack: swap the temp row for the server row in place (new $id → new $files binding)
|
|
2271
|
+
const { store, arr, index } = findRawIndex(dataSourceName, optimisticData.$id);
|
|
2272
|
+
if (store && arr) {
|
|
2214
2273
|
if (index !== -1) {
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
if (i === index) {
|
|
2220
|
-
// Replace with server response
|
|
2221
|
-
return createReactiveReferences
|
|
2222
|
-
? createReactiveReferences(result, dataSourceName)
|
|
2223
|
-
: result;
|
|
2224
|
-
}
|
|
2225
|
-
return entry;
|
|
2226
|
-
});
|
|
2227
|
-
const reactiveArray = createReactiveReferences
|
|
2228
|
-
? createReactiveReferences(newArray, dataSourceName)
|
|
2229
|
-
: newArray;
|
|
2230
|
-
Alpine.store('data', {
|
|
2231
|
-
...store,
|
|
2232
|
-
[dataSourceName]: reactiveArray
|
|
2233
|
-
});
|
|
2234
|
-
// Attach methods
|
|
2235
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
2236
|
-
const loadDataSource = window.ManifestDataMain?.loadDataSource;
|
|
2237
|
-
if (loadDataSource) {
|
|
2238
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveArray, dataSourceName, loadDataSource);
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
// Clear caches
|
|
2242
|
-
if (window.ManifestDataProxies?.clearAccessCache) {
|
|
2243
|
-
window.ManifestDataProxies.clearAccessCache(dataSourceName);
|
|
2244
|
-
}
|
|
2245
|
-
if (window.ManifestDataProxies?.clearArrayProxyCacheForDataSource) {
|
|
2246
|
-
window.ManifestDataProxies.clearArrayProxyCacheForDataSource(dataSourceName);
|
|
2247
|
-
}
|
|
2248
|
-
if (window.ManifestDataProxies?.clearRouteProxyCacheForDataSource) {
|
|
2249
|
-
window.ManifestDataProxies.clearRouteProxyCacheForDataSource(dataSourceName);
|
|
2250
|
-
}
|
|
2274
|
+
const ds = window.ManifestDataStore;
|
|
2275
|
+
store[dataSourceName].splice(index, 1, ds.createReactiveReferences(result, dataSourceName));
|
|
2276
|
+
ds.touchSource(dataSourceName);
|
|
2277
|
+
clearSourceCaches(dataSourceName);
|
|
2251
2278
|
} else {
|
|
2252
2279
|
// Temporary entry not found, just add the real one
|
|
2253
2280
|
addEntryToStore(dataSourceName, result, options);
|
|
@@ -6534,15 +6561,12 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
6534
6561
|
if (store) {
|
|
6535
6562
|
const stateKey = `_${dataSourceName}_state`;
|
|
6536
6563
|
const currentState = store[stateKey] || { loading: false, error: null, ready: false };
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
errorTime: Date.now()
|
|
6543
|
-
}
|
|
6564
|
+
// State-only write, in place (never replace the store object)
|
|
6565
|
+
store[stateKey] = {
|
|
6566
|
+
...currentState,
|
|
6567
|
+
error: error?.message || error || 'Operation failed',
|
|
6568
|
+
errorTime: Date.now()
|
|
6544
6569
|
};
|
|
6545
|
-
Alpine.store('data', updatedStore);
|
|
6546
6570
|
}
|
|
6547
6571
|
console.error(`[Manifest Data] ${dataSourceName} operation failed:`, error);
|
|
6548
6572
|
};
|
|
@@ -6553,15 +6577,7 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
6553
6577
|
const stateKey = `_${dataSourceName}_state`;
|
|
6554
6578
|
const currentState = store[stateKey];
|
|
6555
6579
|
if (currentState?.error) {
|
|
6556
|
-
|
|
6557
|
-
...store,
|
|
6558
|
-
[stateKey]: {
|
|
6559
|
-
...currentState,
|
|
6560
|
-
error: null,
|
|
6561
|
-
errorTime: null
|
|
6562
|
-
}
|
|
6563
|
-
};
|
|
6564
|
-
Alpine.store('data', updatedStore);
|
|
6580
|
+
store[stateKey] = { ...currentState, error: null, errorTime: null };
|
|
6565
6581
|
}
|
|
6566
6582
|
}
|
|
6567
6583
|
};
|
|
@@ -6936,29 +6952,9 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
6936
6952
|
tableId,
|
|
6937
6953
|
appwriteQueries
|
|
6938
6954
|
);
|
|
6939
|
-
//
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
// Create a new array reference to ensure Alpine detects the change
|
|
6943
|
-
const newArray = Array.isArray(result) ? [...result] : result;
|
|
6944
|
-
|
|
6945
|
-
// Use Alpine.store() to replace the entire store, which triggers reactivity
|
|
6946
|
-
const currentStore = Alpine.store('data');
|
|
6947
|
-
const updatedStore = {
|
|
6948
|
-
...currentStore,
|
|
6949
|
-
[dataSourceName]: newArray
|
|
6950
|
-
};
|
|
6951
|
-
Alpine.store('data', updatedStore);
|
|
6952
|
-
|
|
6953
|
-
// Attach methods to the new array reference
|
|
6954
|
-
if (Array.isArray(newArray) && window.ManifestDataProxies?.attachArrayMethods) {
|
|
6955
|
-
window.ManifestDataProxies.attachArrayMethods(newArray, dataSourceName, reloadDataSource);
|
|
6956
|
-
}
|
|
6957
|
-
|
|
6958
|
-
// Clear proxy cache to force fresh read
|
|
6959
|
-
if (window.ManifestDataProxies?.clearAccessCache) {
|
|
6960
|
-
window.ManifestDataProxies.clearAccessCache(dataSourceName);
|
|
6961
|
-
}
|
|
6955
|
+
// Network landing: query result replaces the source (coalesced, identity-preserving)
|
|
6956
|
+
if (result !== undefined && window.ManifestDataStore?.landRows) {
|
|
6957
|
+
await window.ManifestDataStore.landRows(dataSourceName, result, { mode: 'replace' });
|
|
6962
6958
|
}
|
|
6963
6959
|
return result;
|
|
6964
6960
|
}
|
|
@@ -7267,33 +7263,13 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
7267
7263
|
// We already have the real file from the API response, so no
|
|
7268
7264
|
// background reload; scope filtering (if any) is handled by realtime.
|
|
7269
7265
|
if (!addEntryToStore) {
|
|
7270
|
-
// Fallback
|
|
7266
|
+
// Fallback: reload lands through loadDataSource (coalesced landing)
|
|
7271
7267
|
if (window.ManifestDataStore?.dataSourceCache) {
|
|
7272
7268
|
const cacheKey = `${dataSourceName}:en`;
|
|
7273
7269
|
window.ManifestDataStore.dataSourceCache.delete(cacheKey);
|
|
7274
7270
|
}
|
|
7275
7271
|
clearAccessCache(dataSourceName);
|
|
7276
|
-
|
|
7277
|
-
if (reloadedData && Array.isArray(reloadedData) && typeof Alpine !== 'undefined' && Alpine.store) {
|
|
7278
|
-
const store = Alpine.store('data');
|
|
7279
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
7280
|
-
const newArray = createReactiveReferences
|
|
7281
|
-
? createReactiveReferences(reloadedData, dataSourceName)
|
|
7282
|
-
: reloadedData.map(entry => ({ ...entry }));
|
|
7283
|
-
Alpine.store('data', {
|
|
7284
|
-
...store,
|
|
7285
|
-
[dataSourceName]: newArray
|
|
7286
|
-
});
|
|
7287
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
7288
|
-
window.ManifestDataProxies.attachArrayMethods(newArray, dataSourceName, reloadDataSource);
|
|
7289
|
-
}
|
|
7290
|
-
if (window.ManifestDataProxies?.clearAccessCache) {
|
|
7291
|
-
window.ManifestDataProxies.clearAccessCache(dataSourceName);
|
|
7292
|
-
}
|
|
7293
|
-
if (window.ManifestDataProxies?.clearArrayProxyCacheForDataSource) {
|
|
7294
|
-
window.ManifestDataProxies.clearArrayProxyCacheForDataSource(dataSourceName);
|
|
7295
|
-
}
|
|
7296
|
-
}
|
|
7272
|
+
await reloadDataSource(dataSourceName);
|
|
7297
7273
|
}
|
|
7298
7274
|
|
|
7299
7275
|
return result;
|
|
@@ -7677,53 +7653,19 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
7677
7653
|
// Do delete and reload in parallel
|
|
7678
7654
|
const [result, reloadedData] = await Promise.all([deletePromise, reloadPromise]);
|
|
7679
7655
|
|
|
7680
|
-
//
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
const newArray = Array.isArray(reloadedData) ? [...reloadedData] : reloadedData;
|
|
7684
|
-
const currentStore = Alpine.store('data');
|
|
7685
|
-
const updatedStore = {
|
|
7686
|
-
...currentStore,
|
|
7687
|
-
[dataSourceName]: newArray
|
|
7688
|
-
};
|
|
7689
|
-
Alpine.store('data', updatedStore);
|
|
7690
|
-
// Attach methods to the new array reference
|
|
7691
|
-
if (Array.isArray(newArray) && window.ManifestDataProxies?.attachArrayMethods) {
|
|
7692
|
-
window.ManifestDataProxies.attachArrayMethods(newArray, dataSourceName, reloadDataSource);
|
|
7693
|
-
}
|
|
7694
|
-
if (window.ManifestDataProxies?.clearAccessCache) {
|
|
7695
|
-
window.ManifestDataProxies.clearAccessCache(dataSourceName);
|
|
7696
|
-
}
|
|
7697
|
-
if (window.ManifestDataProxies?.clearArrayProxyCacheForDataSource) {
|
|
7698
|
-
window.ManifestDataProxies.clearArrayProxyCacheForDataSource(dataSourceName);
|
|
7699
|
-
}
|
|
7656
|
+
// Network landing: reloaded bucket listing replaces the source (coalesced)
|
|
7657
|
+
if (reloadedData && window.ManifestDataStore?.landRows) {
|
|
7658
|
+
await window.ManifestDataStore.landRows(dataSourceName, reloadedData, { mode: 'replace' });
|
|
7700
7659
|
}
|
|
7701
7660
|
|
|
7702
|
-
// Reload affected table data sources
|
|
7703
|
-
//
|
|
7661
|
+
// Reload affected table data sources so fileIds arrays and counters
|
|
7662
|
+
// update in the UI (loadDataSource lands the rows itself)
|
|
7704
7663
|
const reloadDataSourceFunc = window.ManifestDataMain?._loadDataSource;
|
|
7705
7664
|
if (reloadDataSourceFunc && Object.keys(affectedEntries).length > 0) {
|
|
7706
7665
|
const reloadPromises = [];
|
|
7707
7666
|
for (const tableName of Object.keys(affectedEntries)) {
|
|
7708
7667
|
reloadPromises.push(
|
|
7709
|
-
reloadDataSourceFunc(tableName).
|
|
7710
|
-
// Ensure we create new object references so Alpine detects nested property changes
|
|
7711
|
-
if (reloadedData && Array.isArray(reloadedData) && typeof Alpine !== 'undefined' && Alpine.store) {
|
|
7712
|
-
const store = Alpine.store('data');
|
|
7713
|
-
const newArray = reloadedData.map(entry => ({ ...entry }));
|
|
7714
|
-
Alpine.store('data', {
|
|
7715
|
-
...store,
|
|
7716
|
-
[tableName]: newArray
|
|
7717
|
-
});
|
|
7718
|
-
// Clear cache for reactivity
|
|
7719
|
-
if (window.ManifestDataProxies?.clearAccessCache) {
|
|
7720
|
-
window.ManifestDataProxies.clearAccessCache(tableName);
|
|
7721
|
-
}
|
|
7722
|
-
if (window.ManifestDataProxies?.clearArrayProxyCacheForDataSource) {
|
|
7723
|
-
window.ManifestDataProxies.clearArrayProxyCacheForDataSource(tableName);
|
|
7724
|
-
}
|
|
7725
|
-
}
|
|
7726
|
-
}).catch(err => {
|
|
7668
|
+
reloadDataSourceFunc(tableName).catch(err => {
|
|
7727
7669
|
console.warn(`[Manifest Data] Failed to reload ${tableName} after file delete:`, err);
|
|
7728
7670
|
})
|
|
7729
7671
|
);
|
|
@@ -8348,6 +8290,15 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8348
8290
|
};
|
|
8349
8291
|
}
|
|
8350
8292
|
|
|
8293
|
+
// $x.all — lazy cross-source array, versioned by _v.all
|
|
8294
|
+
if (prop === 'all') {
|
|
8295
|
+
return window.ManifestDataStore?.getAll?.() ?? [];
|
|
8296
|
+
}
|
|
8297
|
+
|
|
8298
|
+
// Per-source subscription: read _v[prop] only (never the whole
|
|
8299
|
+
// store or _dataVersion) so landings elsewhere don't re-run us
|
|
8300
|
+
const track = () => { const v = Alpine.store('data')?._v; if (v) void v[prop]; };
|
|
8301
|
+
|
|
8351
8302
|
// Resolve+cache from raw data BEFORE reading Alpine.store('data'):
|
|
8352
8303
|
// the store read registers a reactive dep that can re-run this
|
|
8353
8304
|
// effect and re-enter get() → stack overflow. Caching first makes
|
|
@@ -8363,9 +8314,7 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8363
8314
|
if (nestedCache.has(prop) && hasData) {
|
|
8364
8315
|
const cachedProxy = nestedCache.get(prop);
|
|
8365
8316
|
if (cachedProxy) {
|
|
8366
|
-
//
|
|
8367
|
-
const store = Alpine.store('data');
|
|
8368
|
-
void (store && store._dataVersion);
|
|
8317
|
+
track(); // locale change / reload re-runs us; still the cached proxy
|
|
8369
8318
|
return cachedProxy;
|
|
8370
8319
|
}
|
|
8371
8320
|
}
|
|
@@ -8378,7 +8327,7 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8378
8327
|
const nestedProxy = createNestedObjectProxy(rawValueEarly, prop, reloadDataSource, []);
|
|
8379
8328
|
if (nestedProxy) {
|
|
8380
8329
|
nestedCache.set(prop, nestedProxy);
|
|
8381
|
-
|
|
8330
|
+
track();
|
|
8382
8331
|
return nestedProxy;
|
|
8383
8332
|
}
|
|
8384
8333
|
} catch (e) {
|
|
@@ -8388,9 +8337,9 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8388
8337
|
}
|
|
8389
8338
|
}
|
|
8390
8339
|
|
|
8391
|
-
// No raw data yet: start load, subscribe (
|
|
8392
|
-
//
|
|
8393
|
-
//
|
|
8340
|
+
// No raw data yet: start load, subscribe to _v[prop] (re-runs when
|
|
8341
|
+
// this source lands), return loading proxy. Reading only the
|
|
8342
|
+
// version avoids re-entry/overflow.
|
|
8394
8343
|
if (!hasData) {
|
|
8395
8344
|
if (!pendingLoads.has(prop)) {
|
|
8396
8345
|
const locale = typeof document !== 'undefined' && document.documentElement
|
|
@@ -8401,8 +8350,7 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8401
8350
|
setTimeout(() => pendingLoads.delete(prop), 1000);
|
|
8402
8351
|
});
|
|
8403
8352
|
}
|
|
8404
|
-
|
|
8405
|
-
void (store && store._dataVersion);
|
|
8353
|
+
track();
|
|
8406
8354
|
const createLoadingProxy = window.ManifestDataProxiesCore?.createLoadingProxy;
|
|
8407
8355
|
if (createLoadingProxy) {
|
|
8408
8356
|
return createLoadingProxy(prop);
|
|
@@ -8412,7 +8360,7 @@ function registerXMagicMethod(loadDataSource) {
|
|
|
8412
8360
|
|
|
8413
8361
|
// Get current store for paths that need it (arrays, or object with raw data for cache/consistency)
|
|
8414
8362
|
const currentStoreForCache = Alpine.store('data');
|
|
8415
|
-
|
|
8363
|
+
track();
|
|
8416
8364
|
|
|
8417
8365
|
// No activeProps circular check here: a legit store-read re-entry
|
|
8418
8366
|
// would be misread as circular and return a loading proxy, breaking
|
|
@@ -10472,38 +10420,15 @@ async function handleStorageRealtimeEvent(dataSourceName, bucketId, scope, event
|
|
|
10472
10420
|
return;
|
|
10473
10421
|
}
|
|
10474
10422
|
|
|
10475
|
-
//
|
|
10476
|
-
const
|
|
10477
|
-
const updateEntryInStore = window.ManifestDataMutations?.updateEntryInStore;
|
|
10478
|
-
const removeEntryFromStore = window.ManifestDataMutations?.removeEntryFromStore;
|
|
10423
|
+
// Network landings: coalesced per frame (landRows upserts by $id), never the sync mutation path
|
|
10424
|
+
const { landRows, landRemove } = window.ManifestDataStore;
|
|
10479
10425
|
|
|
10480
10426
|
if (eventType === 'create') {
|
|
10481
|
-
// New file created - check if it matches scope before adding
|
|
10482
10427
|
const file = payload?.$id ? payload : (payload?.file || payload);
|
|
10483
10428
|
if (file && file.$id) {
|
|
10484
|
-
// Check if file matches current scope
|
|
10485
10429
|
const fileMatchesScope = await checkFileMatchesScope(file, scope);
|
|
10486
10430
|
if (fileMatchesScope) {
|
|
10487
|
-
|
|
10488
|
-
const exists = currentFiles.some(f => f.$id === file.$id);
|
|
10489
|
-
if (!exists && addEntryToStore) {
|
|
10490
|
-
// Use scoped update: add only this file
|
|
10491
|
-
addEntryToStore(dataSourceName, file);
|
|
10492
|
-
} else if (!exists) {
|
|
10493
|
-
// Fallback: update entire array
|
|
10494
|
-
const updatedFiles = [...currentFiles, file];
|
|
10495
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
10496
|
-
const reactiveFiles = createReactiveReferences
|
|
10497
|
-
? createReactiveReferences(updatedFiles, dataSourceName)
|
|
10498
|
-
: updatedFiles;
|
|
10499
|
-
Alpine.store('data', { ...store, [dataSourceName]: reactiveFiles });
|
|
10500
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
10501
|
-
const loadDataSource = window.ManifestDataMain?._loadDataSource;
|
|
10502
|
-
if (loadDataSource) {
|
|
10503
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveFiles, dataSourceName, loadDataSource);
|
|
10504
|
-
}
|
|
10505
|
-
}
|
|
10506
|
-
}
|
|
10431
|
+
landRows(dataSourceName, [file], { mode: 'append' });
|
|
10507
10432
|
|
|
10508
10433
|
// Emit custom event for new file creation so UI can refresh project files
|
|
10509
10434
|
window.dispatchEvent(new CustomEvent('manifest:file-created', {
|
|
@@ -10514,97 +10439,20 @@ async function handleStorageRealtimeEvent(dataSourceName, bucketId, scope, event
|
|
|
10514
10439
|
console.warn('[Manifest Data] Invalid file payload in create event:', payload);
|
|
10515
10440
|
}
|
|
10516
10441
|
} else if (eventType === 'update') {
|
|
10517
|
-
// File updated - update in array if it exists
|
|
10518
10442
|
const file = payload?.$id ? payload : (payload?.file || payload);
|
|
10519
10443
|
if (file && file.$id) {
|
|
10520
|
-
const
|
|
10521
|
-
if (
|
|
10522
|
-
|
|
10523
|
-
const fileMatchesScope = await checkFileMatchesScope(file, scope);
|
|
10524
|
-
if (fileMatchesScope) {
|
|
10525
|
-
// Use scoped update: update only this file
|
|
10526
|
-
if (updateEntryInStore) {
|
|
10527
|
-
updateEntryInStore(dataSourceName, file.$id, file);
|
|
10528
|
-
} else {
|
|
10529
|
-
// Fallback: update entire array
|
|
10530
|
-
const updatedFiles = currentFiles.map(f => f.$id === file.$id ? file : f);
|
|
10531
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
10532
|
-
const reactiveFiles = createReactiveReferences
|
|
10533
|
-
? createReactiveReferences(updatedFiles)
|
|
10534
|
-
: updatedFiles;
|
|
10535
|
-
Alpine.store('data', { ...store, [dataSourceName]: reactiveFiles });
|
|
10536
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
10537
|
-
const loadDataSource = window.ManifestDataMain?._loadDataSource;
|
|
10538
|
-
if (loadDataSource) {
|
|
10539
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveFiles, dataSourceName, loadDataSource);
|
|
10540
|
-
}
|
|
10541
|
-
}
|
|
10542
|
-
}
|
|
10543
|
-
} else {
|
|
10544
|
-
// File no longer matches scope, remove it
|
|
10545
|
-
if (removeEntryFromStore) {
|
|
10546
|
-
removeEntryFromStore(dataSourceName, file.$id);
|
|
10547
|
-
} else {
|
|
10548
|
-
// Fallback: update entire array
|
|
10549
|
-
const updatedFiles = currentFiles.filter(f => f.$id !== file.$id);
|
|
10550
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
10551
|
-
const reactiveFiles = createReactiveReferences
|
|
10552
|
-
? createReactiveReferences(updatedFiles)
|
|
10553
|
-
: updatedFiles;
|
|
10554
|
-
Alpine.store('data', { ...store, [dataSourceName]: reactiveFiles });
|
|
10555
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
10556
|
-
const loadDataSource = window.ManifestDataMain?._loadDataSource;
|
|
10557
|
-
if (loadDataSource) {
|
|
10558
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveFiles, dataSourceName, loadDataSource);
|
|
10559
|
-
}
|
|
10560
|
-
}
|
|
10561
|
-
}
|
|
10562
|
-
}
|
|
10444
|
+
const fileMatchesScope = await checkFileMatchesScope(file, scope);
|
|
10445
|
+
if (fileMatchesScope) {
|
|
10446
|
+
landRows(dataSourceName, [file], { mode: 'append' });
|
|
10563
10447
|
} else {
|
|
10564
|
-
// File
|
|
10565
|
-
|
|
10566
|
-
if (fileMatchesScope && addEntryToStore) {
|
|
10567
|
-
addEntryToStore(dataSourceName, file);
|
|
10568
|
-
} else if (fileMatchesScope) {
|
|
10569
|
-
// Fallback: update entire array
|
|
10570
|
-
const updatedFiles = [...currentFiles, file];
|
|
10571
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
10572
|
-
const reactiveFiles = createReactiveReferences
|
|
10573
|
-
? createReactiveReferences(updatedFiles, dataSourceName)
|
|
10574
|
-
: updatedFiles;
|
|
10575
|
-
Alpine.store('data', { ...store, [dataSourceName]: reactiveFiles });
|
|
10576
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
10577
|
-
const loadDataSource = window.ManifestDataMain?._loadDataSource;
|
|
10578
|
-
if (loadDataSource) {
|
|
10579
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveFiles, dataSourceName, loadDataSource);
|
|
10580
|
-
}
|
|
10581
|
-
}
|
|
10582
|
-
}
|
|
10448
|
+
// File no longer matches scope, remove it
|
|
10449
|
+
landRemove(dataSourceName, [file.$id]);
|
|
10583
10450
|
}
|
|
10584
10451
|
}
|
|
10585
10452
|
} else if (eventType === 'delete') {
|
|
10586
|
-
// File deleted - remove from array
|
|
10587
10453
|
const fileId = payload?.$id || payload?.file?.$id || payload?.fileId || payload;
|
|
10588
10454
|
if (fileId) {
|
|
10589
|
-
|
|
10590
|
-
if (removeEntryFromStore) {
|
|
10591
|
-
// Use scoped update: remove only this file
|
|
10592
|
-
removeEntryFromStore(dataSourceName, actualFileId);
|
|
10593
|
-
} else {
|
|
10594
|
-
// Fallback: update entire array
|
|
10595
|
-
const updatedFiles = currentFiles.filter(f => f.$id !== actualFileId);
|
|
10596
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
10597
|
-
const reactiveFiles = createReactiveReferences
|
|
10598
|
-
? createReactiveReferences(updatedFiles)
|
|
10599
|
-
: updatedFiles;
|
|
10600
|
-
Alpine.store('data', { ...store, [dataSourceName]: reactiveFiles });
|
|
10601
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
10602
|
-
const loadDataSource = window.ManifestDataMain?._loadDataSource;
|
|
10603
|
-
if (loadDataSource) {
|
|
10604
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveFiles, dataSourceName, loadDataSource);
|
|
10605
|
-
}
|
|
10606
|
-
}
|
|
10607
|
-
}
|
|
10455
|
+
landRemove(dataSourceName, [fileId.$id || fileId]);
|
|
10608
10456
|
}
|
|
10609
10457
|
}
|
|
10610
10458
|
}
|
|
@@ -10676,201 +10524,105 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
|
|
|
10676
10524
|
return;
|
|
10677
10525
|
}
|
|
10678
10526
|
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
// Use scoped updates via mutation system
|
|
10682
|
-
const addEntryToStore = window.ManifestDataMutations?.addEntryToStore;
|
|
10683
|
-
const updateEntryInStore = window.ManifestDataMutations?.updateEntryInStore;
|
|
10684
|
-
const removeEntryFromStore = window.ManifestDataMutations?.removeEntryFromStore;
|
|
10527
|
+
// Network landings: coalesced per frame (landRows upserts by $id), never the sync mutation path
|
|
10528
|
+
const { landRows, landRemove } = window.ManifestDataStore;
|
|
10685
10529
|
|
|
10686
10530
|
if (eventType === 'create') {
|
|
10687
|
-
// New row created - check if it matches scope before adding
|
|
10688
10531
|
const row = payload?.$id ? payload : (payload?.row || payload);
|
|
10689
10532
|
if (row && row.$id) {
|
|
10690
|
-
// Check if row matches current scope
|
|
10691
10533
|
const rowMatchesScope = await checkRowMatchesScope(row, scope);
|
|
10692
10534
|
if (rowMatchesScope) {
|
|
10693
|
-
|
|
10694
|
-
const exists = currentRows.some(r => r.$id === row.$id);
|
|
10695
|
-
if (!exists && addEntryToStore) {
|
|
10696
|
-
// Use scoped update: add only this entry
|
|
10697
|
-
addEntryToStore(dataSourceName, row);
|
|
10698
|
-
} else if (!exists) {
|
|
10699
|
-
// Fallback: update entire array
|
|
10700
|
-
const updatedRows = [...currentRows, row];
|
|
10701
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
10702
|
-
const reactiveRows = createReactiveReferences
|
|
10703
|
-
? createReactiveReferences(updatedRows, dataSourceName)
|
|
10704
|
-
: updatedRows;
|
|
10705
|
-
Alpine.store('data', { ...store, [dataSourceName]: reactiveRows });
|
|
10706
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
10707
|
-
const loadDataSource = window.ManifestDataMain?._loadDataSource;
|
|
10708
|
-
if (loadDataSource) {
|
|
10709
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveRows, dataSourceName, loadDataSource);
|
|
10710
|
-
}
|
|
10711
|
-
}
|
|
10712
|
-
}
|
|
10535
|
+
landRows(dataSourceName, [row], { mode: 'append' });
|
|
10713
10536
|
}
|
|
10714
10537
|
} else {
|
|
10715
10538
|
console.warn('[Manifest Data] Invalid row payload in create event:', payload);
|
|
10716
10539
|
}
|
|
10717
10540
|
} else if (eventType === 'update') {
|
|
10718
|
-
// Row updated - update in array if it exists
|
|
10719
10541
|
const row = payload?.$id ? payload : (payload?.row || payload);
|
|
10720
10542
|
if (row && row.$id) {
|
|
10721
10543
|
const existingRow = currentRows.find(r => r.$id === row.$id);
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10725
|
-
if (
|
|
10726
|
-
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
let shouldUpdate = true;
|
|
10734
|
-
|
|
10735
|
-
if (isProjectWithFiles) {
|
|
10736
|
-
const existingUpdatedAt = existingRow?.$updatedAt || existingRow?.$sequence;
|
|
10737
|
-
const newUpdatedAt = row?.$updatedAt || row?.$sequence;
|
|
10738
|
-
const shouldUpdateByTimestamp = !newUpdatedAt || !existingUpdatedAt || newUpdatedAt !== existingUpdatedAt;
|
|
10739
|
-
|
|
10740
|
-
if (!shouldUpdateByTimestamp) {
|
|
10741
|
-
shouldUpdate = false; // identical timestamps → no-op
|
|
10742
|
-
} else {
|
|
10743
|
-
const existingFileIdsSet = new Set(existingFileIds);
|
|
10744
|
-
const incomingFileIdsSet = new Set(incomingFileIds);
|
|
10745
|
-
|
|
10746
|
-
const isSuperset = incomingFileIds.every(id => existingFileIdsSet.has(id)) &&
|
|
10747
|
-
incomingFileIds.length > existingFileIds.length;
|
|
10748
|
-
|
|
10749
|
-
const isMissingFiles = existingFileIds.some(id => !incomingFileIdsSet.has(id));
|
|
10750
|
-
|
|
10751
|
-
const timestampDiff = newUpdatedAt > existingUpdatedAt ?
|
|
10752
|
-
(new Date(newUpdatedAt) - new Date(existingUpdatedAt)) :
|
|
10753
|
-
(new Date(existingUpdatedAt) - new Date(newUpdatedAt));
|
|
10754
|
-
const isDefinitelyNewer = timestampDiff > 1000; // ms
|
|
10755
|
-
|
|
10756
|
-
// Incoming missing files + not clearly newer = stale event
|
|
10757
|
-
// racing an optimistic update; ignore it to protect the upload.
|
|
10758
|
-
if (isMissingFiles && !isDefinitelyNewer) {
|
|
10759
|
-
console.warn('[Manifest Data] Ignoring stale realtime update (protecting optimistic update):', {
|
|
10760
|
-
projectId: row.$id,
|
|
10761
|
-
existingFileIds: existingFileIds,
|
|
10762
|
-
incomingFileIds: incomingFileIds,
|
|
10763
|
-
existingFileIdsCount: existingFileIds.length,
|
|
10764
|
-
incomingFileIdsCount: incomingFileIds.length,
|
|
10765
|
-
existingUpdatedAt,
|
|
10766
|
-
newUpdatedAt,
|
|
10767
|
-
timestampDiff,
|
|
10768
|
-
isMissingFiles,
|
|
10769
|
-
isDefinitelyNewer,
|
|
10770
|
-
reason: 'Incoming data missing files that exist in store - likely stale realtime event'
|
|
10771
|
-
});
|
|
10772
|
-
shouldUpdate = false;
|
|
10773
|
-
}
|
|
10774
|
-
}
|
|
10775
|
-
}
|
|
10544
|
+
const rowMatchesScope = await checkRowMatchesScope(row, scope);
|
|
10545
|
+
if (!rowMatchesScope) {
|
|
10546
|
+
// Row no longer matches scope, remove it
|
|
10547
|
+
if (existingRow) landRemove(dataSourceName, [row.$id]);
|
|
10548
|
+
return;
|
|
10549
|
+
}
|
|
10550
|
+
if (!existingRow) {
|
|
10551
|
+
// Row not in list, but matches scope now - add it
|
|
10552
|
+
landRows(dataSourceName, [row], { mode: 'append' });
|
|
10553
|
+
return;
|
|
10554
|
+
}
|
|
10776
10555
|
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10781
|
-
|
|
10556
|
+
// Most sources update unconditionally; projects-with-fileIds get
|
|
10557
|
+
// special handling to protect optimistic uploads (see below).
|
|
10558
|
+
const existingFileIds = existingRow?.fileIds || [];
|
|
10559
|
+
const incomingFileIds = row?.fileIds || [];
|
|
10560
|
+
const hasFileIds = existingFileIds.length > 0 || incomingFileIds.length > 0;
|
|
10561
|
+
const isProjectWithFiles = hasFileIds && dataSourceName === 'projects';
|
|
10782
10562
|
|
|
10783
|
-
|
|
10784
|
-
if (updateEntryInStore) {
|
|
10785
|
-
updateEntryInStore(dataSourceName, row.$id, row);
|
|
10786
|
-
} else {
|
|
10787
|
-
// Fallback: update entire array
|
|
10788
|
-
const updatedRows = currentRows.map(r => r.$id === row.$id ? row : r);
|
|
10789
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
10790
|
-
const reactiveRows = createReactiveReferences
|
|
10791
|
-
? createReactiveReferences(updatedRows, dataSourceName)
|
|
10792
|
-
: updatedRows;
|
|
10793
|
-
Alpine.store('data', { ...store, [dataSourceName]: reactiveRows });
|
|
10794
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
10795
|
-
const loadDataSource = window.ManifestDataMain?._loadDataSource;
|
|
10796
|
-
if (loadDataSource) {
|
|
10797
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveRows, dataSourceName, loadDataSource);
|
|
10798
|
-
}
|
|
10799
|
-
}
|
|
10800
|
-
}
|
|
10563
|
+
let shouldUpdate = true;
|
|
10801
10564
|
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10565
|
+
if (isProjectWithFiles) {
|
|
10566
|
+
const existingUpdatedAt = existingRow?.$updatedAt || existingRow?.$sequence;
|
|
10567
|
+
const newUpdatedAt = row?.$updatedAt || row?.$sequence;
|
|
10568
|
+
const shouldUpdateByTimestamp = !newUpdatedAt || !existingUpdatedAt || newUpdatedAt !== existingUpdatedAt;
|
|
10569
|
+
|
|
10570
|
+
if (!shouldUpdateByTimestamp) {
|
|
10571
|
+
shouldUpdate = false; // identical timestamps → no-op
|
|
10809
10572
|
} else {
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
|
|
10822
|
-
|
|
10823
|
-
|
|
10824
|
-
|
|
10825
|
-
|
|
10826
|
-
|
|
10573
|
+
const existingFileIdsSet = new Set(existingFileIds);
|
|
10574
|
+
const incomingFileIdsSet = new Set(incomingFileIds);
|
|
10575
|
+
|
|
10576
|
+
const isSuperset = incomingFileIds.every(id => existingFileIdsSet.has(id)) &&
|
|
10577
|
+
incomingFileIds.length > existingFileIds.length;
|
|
10578
|
+
|
|
10579
|
+
const isMissingFiles = existingFileIds.some(id => !incomingFileIdsSet.has(id));
|
|
10580
|
+
|
|
10581
|
+
const timestampDiff = newUpdatedAt > existingUpdatedAt ?
|
|
10582
|
+
(new Date(newUpdatedAt) - new Date(existingUpdatedAt)) :
|
|
10583
|
+
(new Date(existingUpdatedAt) - new Date(newUpdatedAt));
|
|
10584
|
+
const isDefinitelyNewer = timestampDiff > 1000; // ms
|
|
10585
|
+
|
|
10586
|
+
// Incoming missing files + not clearly newer = stale event
|
|
10587
|
+
// racing an optimistic update; ignore it to protect the upload.
|
|
10588
|
+
if (isMissingFiles && !isDefinitelyNewer) {
|
|
10589
|
+
console.warn('[Manifest Data] Ignoring stale realtime update (protecting optimistic update):', {
|
|
10590
|
+
projectId: row.$id,
|
|
10591
|
+
existingFileIds: existingFileIds,
|
|
10592
|
+
incomingFileIds: incomingFileIds,
|
|
10593
|
+
existingFileIdsCount: existingFileIds.length,
|
|
10594
|
+
incomingFileIdsCount: incomingFileIds.length,
|
|
10595
|
+
existingUpdatedAt,
|
|
10596
|
+
newUpdatedAt,
|
|
10597
|
+
timestampDiff,
|
|
10598
|
+
isMissingFiles,
|
|
10599
|
+
isDefinitelyNewer,
|
|
10600
|
+
reason: 'Incoming data missing files that exist in store - likely stale realtime event'
|
|
10601
|
+
});
|
|
10602
|
+
shouldUpdate = false;
|
|
10827
10603
|
}
|
|
10828
10604
|
}
|
|
10829
|
-
}
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
if (
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
const loadDataSource = window.ManifestDataMain?._loadDataSource;
|
|
10844
|
-
if (loadDataSource) {
|
|
10845
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveRows, dataSourceName, loadDataSource);
|
|
10846
|
-
}
|
|
10847
|
-
}
|
|
10605
|
+
}
|
|
10606
|
+
|
|
10607
|
+
if (shouldUpdate) {
|
|
10608
|
+
// Check if fileIds changed (for projects with linked files)
|
|
10609
|
+
const fileIdsChanged = isProjectWithFiles &&
|
|
10610
|
+
JSON.stringify(existingFileIds) !== JSON.stringify(incomingFileIds);
|
|
10611
|
+
|
|
10612
|
+
landRows(dataSourceName, [row], { mode: 'append' });
|
|
10613
|
+
|
|
10614
|
+
// Emit custom event for project file updates so UI can refresh
|
|
10615
|
+
if (fileIdsChanged && dataSourceName === 'projects') {
|
|
10616
|
+
window.dispatchEvent(new CustomEvent('manifest:project-files-updated', {
|
|
10617
|
+
detail: { projectId: row.$id, fileIds: row.fileIds }
|
|
10618
|
+
}));
|
|
10848
10619
|
}
|
|
10849
10620
|
}
|
|
10850
10621
|
}
|
|
10851
10622
|
} else if (eventType === 'delete') {
|
|
10852
|
-
// Row deleted - remove from array
|
|
10853
10623
|
const rowId = payload?.$id || payload?.row?.$id || payload?.rowId || payload;
|
|
10854
10624
|
if (rowId) {
|
|
10855
|
-
|
|
10856
|
-
if (removeEntryFromStore) {
|
|
10857
|
-
// Use scoped update: remove only this entry
|
|
10858
|
-
removeEntryFromStore(dataSourceName, actualRowId);
|
|
10859
|
-
} else {
|
|
10860
|
-
// Fallback: update entire array
|
|
10861
|
-
const updatedRows = currentRows.filter(r => r.$id !== actualRowId);
|
|
10862
|
-
const createReactiveReferences = window.ManifestDataStore?.createReactiveReferences;
|
|
10863
|
-
const reactiveRows = createReactiveReferences
|
|
10864
|
-
? createReactiveReferences(updatedRows, dataSourceName)
|
|
10865
|
-
: updatedRows;
|
|
10866
|
-
Alpine.store('data', { ...store, [dataSourceName]: reactiveRows });
|
|
10867
|
-
if (window.ManifestDataProxies?.attachArrayMethods) {
|
|
10868
|
-
const loadDataSource = window.ManifestDataMain?._loadDataSource;
|
|
10869
|
-
if (loadDataSource) {
|
|
10870
|
-
window.ManifestDataProxies.attachArrayMethods(reactiveRows, dataSourceName, loadDataSource);
|
|
10871
|
-
}
|
|
10872
|
-
}
|
|
10873
|
-
}
|
|
10625
|
+
landRemove(dataSourceName, [rowId.$id || rowId]);
|
|
10874
10626
|
}
|
|
10875
10627
|
}
|
|
10876
10628
|
|
|
@@ -11002,7 +10754,7 @@ async function checkFileMatchesScope(file, scope) {
|
|
|
11002
10754
|
// Load dataSource data
|
|
11003
10755
|
async function loadDataSource(dataSourceName, locale = 'en') {
|
|
11004
10756
|
const cacheKey = `${dataSourceName}:${locale}`;
|
|
11005
|
-
const { dataSourceCache, loadingPromises, isInitializing, updateStore } = window.ManifestDataStore;
|
|
10757
|
+
const { dataSourceCache, loadingPromises, isInitializing, updateStore, landRows } = window.ManifestDataStore;
|
|
11006
10758
|
|
|
11007
10759
|
// Memory cache. Serving cached data is fine, but writing an old locale to the
|
|
11008
10760
|
// live store would clobber a concurrent locale switch — so guard the store
|
|
@@ -11013,7 +10765,8 @@ async function loadDataSource(dataSourceName, locale = 'en') {
|
|
|
11013
10765
|
|| document.documentElement.lang || locale;
|
|
11014
10766
|
const staleLocaleHit = locale !== liveLocale && !!(cachedData && cachedData._locale);
|
|
11015
10767
|
if (!isInitializing && !staleLocaleHit) {
|
|
11016
|
-
|
|
10768
|
+
// Landing (coalesced): resolves once the cached rows are visible
|
|
10769
|
+
await landRows(dataSourceName, cachedData, { mode: 'replace', loading: false, error: null, ready: true });
|
|
11017
10770
|
}
|
|
11018
10771
|
return cachedData;
|
|
11019
10772
|
}
|
|
@@ -11290,10 +11043,9 @@ async function loadDataSource(dataSourceName, locale = 'en') {
|
|
|
11290
11043
|
|| document.documentElement.lang || locale;
|
|
11291
11044
|
const staleLocale = localeSensitive && liveLocale !== locale;
|
|
11292
11045
|
|
|
11293
|
-
//
|
|
11294
|
-
// Note: updateStore will seal the data to prevent Alpine from proxying it
|
|
11046
|
+
// Network landing (coalesced per frame); resolves once visible
|
|
11295
11047
|
if (!isInitializing && !staleLocale) {
|
|
11296
|
-
|
|
11048
|
+
await landRows(dataSourceName, enhancedData, { mode: 'replace', loading: false, error: null, ready: true });
|
|
11297
11049
|
}
|
|
11298
11050
|
|
|
11299
11051
|
// Return unsealed version for our proxy system
|
|
@@ -11413,7 +11165,7 @@ function setupUrlChangeListeners() {
|
|
|
11413
11165
|
// Initialize plugin when either DOM is ready or Alpine is ready
|
|
11414
11166
|
async function initializeDataSourcesPlugin() {
|
|
11415
11167
|
|
|
11416
|
-
const { initializeStore, setupLocaleChangeListener, setupTeamChangeListener, setIsInitializing, setInitializationComplete, updateStore } = window.ManifestDataStore;
|
|
11168
|
+
const { initializeStore, setupLocaleChangeListener, setupTeamChangeListener, setIsInitializing, setInitializationComplete, updateStore, landRows } = window.ManifestDataStore;
|
|
11417
11169
|
|
|
11418
11170
|
// Initialize empty data sources store
|
|
11419
11171
|
initializeStore();
|
|
@@ -11484,16 +11236,11 @@ async function initializeDataSourcesPlugin() {
|
|
|
11484
11236
|
setIsInitializing(true);
|
|
11485
11237
|
|
|
11486
11238
|
try {
|
|
11487
|
-
//
|
|
11488
|
-
const
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
_initialized: true,
|
|
11493
|
-
_ready: false,
|
|
11494
|
-
// Ensure _currentUrl is preserved or initialized
|
|
11495
|
-
_currentUrl: existingStore._currentUrl || window.location.pathname
|
|
11496
|
-
});
|
|
11239
|
+
// Mark the store initialized in place (never replace the store object)
|
|
11240
|
+
const dataStore = Alpine.store('data');
|
|
11241
|
+
dataStore._initialized = true;
|
|
11242
|
+
dataStore._ready = false;
|
|
11243
|
+
if (!dataStore._currentUrl) dataStore._currentUrl = window.location.pathname;
|
|
11497
11244
|
|
|
11498
11245
|
// Pre-load local file-backed sources so $x.* renders real data first pass;
|
|
11499
11246
|
// Appwrite, API-URL, and "manifest" sources stay on-demand.
|
|
@@ -11516,8 +11263,9 @@ async function initializeDataSourcesPlugin() {
|
|
|
11516
11263
|
preloadNames.map(async (name) => {
|
|
11517
11264
|
try {
|
|
11518
11265
|
const data = await loadDataSource(name, locale);
|
|
11519
|
-
if (data != null
|
|
11520
|
-
|
|
11266
|
+
if (data != null) {
|
|
11267
|
+
// Boot landing: every pre-loaded source lands in ONE flush
|
|
11268
|
+
await landRows(name, data, { mode: 'replace', loading: false, error: null, ready: true, allowDuringInit: true });
|
|
11521
11269
|
}
|
|
11522
11270
|
} catch (err) {
|
|
11523
11271
|
console.warn(`[Manifest Data] Failed to pre-load ${name}:`, err);
|
|
@@ -11536,36 +11284,17 @@ async function initializeDataSourcesPlugin() {
|
|
|
11536
11284
|
// flag updateStore short-circuits and $x.manifest stays unpopulated.
|
|
11537
11285
|
window.ManifestDataStore.dataSourceCache.set(`manifest:${locale}`, publicManifest);
|
|
11538
11286
|
updateStore('manifest', publicManifest, { loading: false, error: null, ready: true, allowDuringInit: true });
|
|
11539
|
-
|
|
11540
|
-
const store = Alpine.store('data');
|
|
11541
|
-
Alpine.store('data', {
|
|
11542
|
-
...store,
|
|
11543
|
-
_ready: true
|
|
11544
|
-
});
|
|
11545
|
-
} else {
|
|
11546
|
-
// No manifest data source, mark as ready anyway
|
|
11547
|
-
const store = Alpine.store('data');
|
|
11548
|
-
Alpine.store('data', {
|
|
11549
|
-
...store,
|
|
11550
|
-
_ready: true
|
|
11551
|
-
});
|
|
11552
11287
|
}
|
|
11288
|
+
Alpine.store('data')._ready = true;
|
|
11553
11289
|
} catch (error) {
|
|
11554
11290
|
// If manifest pre-load fails, mark as ready anyway - it will load on-demand
|
|
11555
11291
|
console.warn('[Manifest Data] Failed to pre-load manifest:', error);
|
|
11556
|
-
|
|
11557
|
-
Alpine.store('data', {
|
|
11558
|
-
...store,
|
|
11559
|
-
_ready: true
|
|
11560
|
-
});
|
|
11292
|
+
Alpine.store('data')._ready = true;
|
|
11561
11293
|
}
|
|
11562
11294
|
|
|
11563
11295
|
// Force Alpine to re-run effects that read $x.content (they may have run before pre-load and got loading proxy)
|
|
11564
11296
|
const flushThenDispatch = () => {
|
|
11565
|
-
|
|
11566
|
-
const s = Alpine.store('data');
|
|
11567
|
-
Alpine.store('data', { ...s, _dataVersion: (s._dataVersion || 0) + 1 });
|
|
11568
|
-
}
|
|
11297
|
+
window.ManifestDataStore?.bumpAllVersions?.();
|
|
11569
11298
|
if (typeof window !== 'undefined') {
|
|
11570
11299
|
window.dispatchEvent(new CustomEvent('manifest:data-ready'));
|
|
11571
11300
|
}
|