mnfst 0.5.272 → 0.5.274

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.272';
5
+ const MANIFEST_BUILD_VERSION = '0.5.274';
6
6
 
7
7
  /* Manifest Data Sources - Appwrite Integration */
8
8
 
@@ -1642,331 +1642,87 @@ window.ManifestDataQueries = {
1642
1642
 
1643
1643
  /* Manifest Data Sources - Pagination (Appwrite) */
1644
1644
 
1645
- // First page, cursor-based
1646
- // Returns { items, cursor, total, hasMore }
1647
- async function getFirstPage(dataSourceName, limit, baseQueries = []) {
1645
+ // Resolve the source's table or bucket
1646
+ async function resolvePagedSource(dataSourceName) {
1648
1647
  const manifest = await window.ManifestDataConfig.ensureManifest();
1649
1648
  if (!manifest?.data) {
1650
1649
  throw new Error('[Manifest Data] Manifest not available');
1651
1650
  }
1652
-
1653
1651
  const dataSource = manifest.data[dataSourceName];
1654
1652
  if (!dataSource) {
1655
1653
  throw new Error(`[Manifest Data] Data source "${dataSourceName}" not found`);
1656
1654
  }
1657
-
1658
- // Check if this is an Appwrite data source
1659
- const isAppwriteTable = window.ManifestDataConfig.isAppwriteCollection(dataSource);
1660
- if (!isAppwriteTable) {
1655
+ if (!window.ManifestDataConfig.isAppwriteCollection(dataSource)) {
1661
1656
  throw new Error(`[Manifest Data] Pagination is only supported for Appwrite data sources`);
1662
1657
  }
1663
-
1664
1658
  const appwriteConfig = await window.ManifestDataConfig.getAppwriteConfig(dataSource);
1665
1659
  if (!appwriteConfig) {
1666
1660
  throw new Error(`[Manifest Data] Invalid Appwrite configuration for "${dataSourceName}"`);
1667
1661
  }
1668
-
1669
1662
  const tableId = window.ManifestDataConfig.getAppwriteTableId(dataSource);
1670
1663
  const bucketId = window.ManifestDataConfig.getAppwriteBucketId(dataSource);
1664
+ if (!tableId && !bucketId) {
1665
+ throw new Error(`[Manifest Data] Data source "${dataSourceName}" is not a table or bucket`);
1666
+ }
1667
+ return { appwriteConfig, tableId, bucketId };
1668
+ }
1671
1669
 
1672
- // Build queries with limit
1673
- const queries = [
1674
- ...baseQueries,
1675
- window.Appwrite.Query.limit(limit)
1676
- ];
1677
-
1670
+ // One page request. Asks for limit + 1 rows: the extra row only answers "is there more"
1671
+ async function fetchPage(dataSourceName, baseQueries, pageQueries, limit) {
1672
+ const { appwriteConfig, tableId, bucketId } = await resolvePagedSource(dataSourceName);
1673
+ const queries = [...baseQueries, ...pageQueries, window.Appwrite.Query.limit(limit + 1)];
1674
+ const services = await window.ManifestDataAppwrite._getAppwriteDataServices();
1678
1675
  let response;
1679
1676
  if (tableId) {
1680
- // Table pagination - need to call Appwrite directly to get full response with total
1681
- const services = await window.ManifestDataAppwrite._getAppwriteDataServices();
1682
- if (!services?.tablesDB) {
1683
- throw new Error('[Manifest Data] Appwrite TablesDB service not available');
1684
- }
1685
-
1686
- const appwriteResponse = await services.tablesDB.listRows({
1687
- databaseId: appwriteConfig.databaseId,
1688
- tableId: tableId,
1689
- queries: queries
1690
- });
1691
-
1692
- const items = appwriteResponse.rows || [];
1693
- const total = appwriteResponse.total || 0;
1694
- const cursor = items.length > 0 ? items[items.length - 1].$id : null;
1695
- const hasMore = items.length === limit && total > limit;
1696
-
1697
- return {
1698
- items,
1699
- cursor,
1700
- total,
1701
- hasMore
1702
- };
1703
- } else if (bucketId) {
1704
- // Storage pagination
1705
- const services = await window.ManifestDataAppwrite._getAppwriteDataServices();
1706
- if (!services?.storage) {
1707
- throw new Error('[Manifest Data] Appwrite Storage service not available');
1708
- }
1709
-
1710
- const storageResponse = await services.storage.listFiles(bucketId, queries);
1711
- const items = storageResponse.files || [];
1712
- const total = storageResponse.total || 0;
1713
- const cursor = items.length > 0 ? items[items.length - 1].$id : null;
1714
- const hasMore = items.length === limit && total > limit;
1715
-
1716
- return {
1717
- items,
1718
- cursor,
1719
- total,
1720
- hasMore
1721
- };
1677
+ if (!services?.tablesDB) throw new Error('[Manifest Data] Appwrite TablesDB service not available');
1678
+ response = await services.tablesDB.listRows({ databaseId: appwriteConfig.databaseId, tableId, queries });
1722
1679
  } else {
1723
- throw new Error(`[Manifest Data] Data source "${dataSourceName}" is not a table or bucket`);
1680
+ if (!services?.storage) throw new Error('[Manifest Data] Appwrite Storage service not available');
1681
+ response = await services.storage.listFiles(bucketId, queries);
1724
1682
  }
1683
+ const rows = (tableId ? response.rows : response.files) || [];
1684
+ return { rows, total: response.total || 0, extra: rows.length > limit };
1725
1685
  }
1726
1686
 
1727
- // Next page from cursor
1728
- // Returns { items, cursor, total, hasMore }
1687
+ // First page, cursor-based. Returns { items, cursor, total, hasMore }
1688
+ async function getFirstPage(dataSourceName, limit, baseQueries = []) {
1689
+ const { rows, total, extra } = await fetchPage(dataSourceName, baseQueries, [], limit);
1690
+ const items = rows.slice(0, limit);
1691
+ return { items, cursor: items.length ? items[items.length - 1].$id : null, total, hasMore: extra };
1692
+ }
1693
+
1694
+ // Next page after a cursor. Returns { items, cursor, total, hasMore }
1729
1695
  async function getNextPage(dataSourceName, cursor, limit, baseQueries = []) {
1730
1696
  if (!cursor) {
1731
1697
  throw new Error('[Manifest Data] Cursor is required for next page');
1732
1698
  }
1733
-
1734
- const manifest = await window.ManifestDataConfig.ensureManifest();
1735
- if (!manifest?.data) {
1736
- throw new Error('[Manifest Data] Manifest not available');
1737
- }
1738
-
1739
- const dataSource = manifest.data[dataSourceName];
1740
- if (!dataSource) {
1741
- throw new Error(`[Manifest Data] Data source "${dataSourceName}" not found`);
1742
- }
1743
-
1744
- const appwriteConfig = await window.ManifestDataConfig.getAppwriteConfig(dataSource);
1745
- if (!appwriteConfig) {
1746
- throw new Error(`[Manifest Data] Invalid Appwrite configuration for "${dataSourceName}"`);
1747
- }
1748
-
1749
- const tableId = window.ManifestDataConfig.getAppwriteTableId(dataSource);
1750
- const bucketId = window.ManifestDataConfig.getAppwriteBucketId(dataSource);
1751
-
1752
- // Build queries with cursorAfter and limit
1753
- const queries = [
1754
- ...baseQueries,
1755
- window.Appwrite.Query.cursorAfter(cursor),
1756
- window.Appwrite.Query.limit(limit)
1757
- ];
1758
-
1759
- let response;
1760
- if (tableId) {
1761
- const services = await window.ManifestDataAppwrite._getAppwriteDataServices();
1762
- if (!services?.tablesDB) {
1763
- throw new Error('[Manifest Data] Appwrite TablesDB service not available');
1764
- }
1765
-
1766
- const appwriteResponse = await services.tablesDB.listRows({
1767
- databaseId: appwriteConfig.databaseId,
1768
- tableId: tableId,
1769
- queries: queries
1770
- });
1771
-
1772
- const items = appwriteResponse.rows || [];
1773
- const total = appwriteResponse.total || 0;
1774
- const newCursor = items.length > 0 ? items[items.length - 1].$id : null;
1775
- const hasMore = items.length === limit && total > limit;
1776
-
1777
- return {
1778
- items,
1779
- cursor: newCursor,
1780
- total,
1781
- hasMore
1782
- };
1783
- } else if (bucketId) {
1784
- const services = await window.ManifestDataAppwrite._getAppwriteDataServices();
1785
- if (!services?.storage) {
1786
- throw new Error('[Manifest Data] Appwrite Storage service not available');
1787
- }
1788
-
1789
- const storageResponse = await services.storage.listFiles(bucketId, queries);
1790
- const items = storageResponse.files || [];
1791
- const total = storageResponse.total || 0;
1792
- const newCursor = items.length > 0 ? items[items.length - 1].$id : null;
1793
- const hasMore = items.length === limit && total > (limit * 2); // Rough estimate
1794
-
1795
- return {
1796
- items,
1797
- cursor: newCursor,
1798
- total,
1799
- hasMore
1800
- };
1801
- } else {
1802
- throw new Error(`[Manifest Data] Data source "${dataSourceName}" is not a table or bucket`);
1803
- }
1699
+ const { rows, total, extra } = await fetchPage(dataSourceName, baseQueries, [window.Appwrite.Query.cursorAfter(cursor)], limit);
1700
+ const items = rows.slice(0, limit);
1701
+ return { items, cursor: items.length ? items[items.length - 1].$id : null, total, hasMore: extra };
1804
1702
  }
1805
1703
 
1806
- // Previous page from cursor
1807
- // Returns { items, cursor, total, hasMore }
1704
+ // Previous page before a cursor. hasMore: rows exist before this page. Returns { items, cursor, total, hasMore }
1808
1705
  async function getPrevPage(dataSourceName, cursor, limit, baseQueries = []) {
1809
1706
  if (!cursor) {
1810
1707
  throw new Error('[Manifest Data] Cursor is required for previous page');
1811
1708
  }
1812
-
1813
- const manifest = await window.ManifestDataConfig.ensureManifest();
1814
- if (!manifest?.data) {
1815
- throw new Error('[Manifest Data] Manifest not available');
1816
- }
1817
-
1818
- const dataSource = manifest.data[dataSourceName];
1819
- if (!dataSource) {
1820
- throw new Error(`[Manifest Data] Data source "${dataSourceName}" not found`);
1821
- }
1822
-
1823
- const appwriteConfig = await window.ManifestDataConfig.getAppwriteConfig(dataSource);
1824
- if (!appwriteConfig) {
1825
- throw new Error(`[Manifest Data] Invalid Appwrite configuration for "${dataSourceName}"`);
1826
- }
1827
-
1828
- const tableId = window.ManifestDataConfig.getAppwriteTableId(dataSource);
1829
- const bucketId = window.ManifestDataConfig.getAppwriteBucketId(dataSource);
1830
-
1831
- // Build queries with cursorBefore and limit
1832
- const queries = [
1833
- ...baseQueries,
1834
- window.Appwrite.Query.cursorBefore(cursor),
1835
- window.Appwrite.Query.limit(limit)
1836
- ];
1837
-
1838
- let response;
1839
- if (tableId) {
1840
- const services = await window.ManifestDataAppwrite._getAppwriteDataServices();
1841
- if (!services?.tablesDB) {
1842
- throw new Error('[Manifest Data] Appwrite TablesDB service not available');
1843
- }
1844
-
1845
- const appwriteResponse = await services.tablesDB.listRows({
1846
- databaseId: appwriteConfig.databaseId,
1847
- tableId: tableId,
1848
- queries: queries
1849
- });
1850
-
1851
- const items = appwriteResponse.rows || [];
1852
- const total = appwriteResponse.total || 0;
1853
- const newCursor = items.length > 0 ? items[0].$id : null; // First item's ID for going back further
1854
- const hasMore = true; // Can't easily determine if there's a previous page
1855
-
1856
- return {
1857
- items,
1858
- cursor: newCursor,
1859
- total,
1860
- hasMore
1861
- };
1862
- } else if (bucketId) {
1863
- const services = await window.ManifestDataAppwrite._getAppwriteDataServices();
1864
- if (!services?.storage) {
1865
- throw new Error('[Manifest Data] Appwrite Storage service not available');
1866
- }
1867
-
1868
- const storageResponse = await services.storage.listFiles(bucketId, queries);
1869
- const items = storageResponse.files || [];
1870
- const total = storageResponse.total || 0;
1871
- const newCursor = items.length > 0 ? items[0].$id : null;
1872
- const hasMore = true;
1873
-
1874
- return {
1875
- items,
1876
- cursor: newCursor,
1877
- total,
1878
- hasMore
1879
- };
1880
- } else {
1881
- throw new Error(`[Manifest Data] Data source "${dataSourceName}" is not a table or bucket`);
1882
- }
1709
+ const { rows, total, extra } = await fetchPage(dataSourceName, baseQueries, [window.Appwrite.Query.cursorBefore(cursor)], limit);
1710
+ // cursorBefore returns the rows nearest the cursor last; the extra row is the first one
1711
+ const items = extra ? rows.slice(rows.length - limit) : rows;
1712
+ return { items, cursor: items.length ? items[0].$id : null, total, hasMore: extra };
1883
1713
  }
1884
1714
 
1885
- // Specific page (1-based), offset-based
1886
- // Returns { items, page, total, totalPages, hasMore }
1715
+ // Specific page (1-based), offset-based. Returns { items, page, total, totalPages, hasMore }
1887
1716
  async function getPage(dataSourceName, pageNumber, limit, baseQueries = []) {
1888
- if (pageNumber < 1) {
1717
+ if (!pageNumber || pageNumber < 1) {
1889
1718
  throw new Error('[Manifest Data] Page number must be >= 1');
1890
1719
  }
1891
-
1892
- const manifest = await window.ManifestDataConfig.ensureManifest();
1893
- if (!manifest?.data) {
1894
- throw new Error('[Manifest Data] Manifest not available');
1895
- }
1896
-
1897
- const dataSource = manifest.data[dataSourceName];
1898
- if (!dataSource) {
1899
- throw new Error(`[Manifest Data] Data source "${dataSourceName}" not found`);
1900
- }
1901
-
1902
- const appwriteConfig = await window.ManifestDataConfig.getAppwriteConfig(dataSource);
1903
- if (!appwriteConfig) {
1904
- throw new Error(`[Manifest Data] Invalid Appwrite configuration for "${dataSourceName}"`);
1905
- }
1906
-
1907
- const tableId = window.ManifestDataConfig.getAppwriteTableId(dataSource);
1908
- const bucketId = window.ManifestDataConfig.getAppwriteBucketId(dataSource);
1909
-
1910
1720
  const offset = (pageNumber - 1) * limit;
1911
-
1912
- // Build queries with offset and limit
1913
- const queries = [
1914
- ...baseQueries,
1915
- window.Appwrite.Query.offset(offset),
1916
- window.Appwrite.Query.limit(limit)
1917
- ];
1918
-
1919
- let response;
1920
- let total;
1921
- if (tableId) {
1922
- const services = await window.ManifestDataAppwrite._getAppwriteDataServices();
1923
- if (!services?.tablesDB) {
1924
- throw new Error('[Manifest Data] Appwrite TablesDB service not available');
1925
- }
1926
-
1927
- const appwriteResponse = await services.tablesDB.listRows({
1928
- databaseId: appwriteConfig.databaseId,
1929
- tableId: tableId,
1930
- queries: queries
1931
- });
1932
-
1933
- const items = appwriteResponse.rows || [];
1934
- total = appwriteResponse.total || 0;
1935
- const totalPages = Math.ceil(total / limit);
1936
- const hasMore = pageNumber < totalPages;
1937
-
1938
- return {
1939
- items,
1940
- page: pageNumber,
1941
- total,
1942
- totalPages,
1943
- hasMore
1944
- };
1945
- } else if (bucketId) {
1946
- const services = await window.ManifestDataAppwrite._getAppwriteDataServices();
1947
- if (!services?.storage) {
1948
- throw new Error('[Manifest Data] Appwrite Storage service not available');
1949
- }
1950
-
1951
- const storageResponse = await services.storage.listFiles(bucketId, queries);
1952
- const items = storageResponse.files || [];
1953
- total = storageResponse.total || 0;
1954
- const totalPages = Math.ceil(total / limit);
1955
- const hasMore = pageNumber < totalPages;
1956
-
1957
- return {
1958
- items,
1959
- page: pageNumber,
1960
- total,
1961
- totalPages,
1962
- hasMore
1963
- };
1964
- } else {
1965
- throw new Error(`[Manifest Data] Data source "${dataSourceName}" is not a table or bucket`);
1966
- }
1721
+ const { rows, total, extra } = await fetchPage(dataSourceName, baseQueries, [window.Appwrite.Query.offset(offset)], limit);
1722
+ const items = rows.slice(0, limit);
1723
+ return { items, page: pageNumber, total, totalPages: Math.ceil(total / limit), hasMore: extra };
1967
1724
  }
1968
1725
 
1969
- // Export functions
1970
1726
  window.ManifestDataPagination = {
1971
1727
  getFirstPage,
1972
1728
  getNextPage,
@@ -205,6 +205,13 @@ function escapeForSingleQuotedJsString(s) {
205
205
  // Framework web components (not project components) — never fetched.
206
206
  const FRAMEWORK_COMPONENT_TAGS = new Set(['code', 'code-group']);
207
207
 
208
+ // Classic JS or module; anything else (JSON, templates) is a data block
209
+ function isExecutableScript(el) {
210
+ const type = (el.getAttribute('type') || '').split(';')[0].trim().toLowerCase();
211
+ return type === '' || type === 'module' || /^(text|application)\/(x-)?(javascript|ecmascript)$/.test(type) ||
212
+ /^text\/(javascript1\.[0-5]|jscript|livescript)$/.test(type);
213
+ }
214
+
208
215
  // The <template x-if>/<template x-for> whose tracked clone is `el`, if any.
209
216
  function findOwningTemplate(el) {
210
217
  if (!el._x_dataStack) return null;
@@ -289,6 +296,8 @@ window.ManifestComponentsProcessor = {
289
296
  const scripts = [];
290
297
  const processScripts = (el) => {
291
298
  if (el.tagName.toLowerCase() === 'script') {
299
+ // Data blocks (JSON, templates) are dropped, never run
300
+ if (!isExecutableScript(el)) { el.remove(); return; }
292
301
  scripts.push({
293
302
  content: el.textContent,
294
303
  type: el.getAttribute('type') || 'text/javascript',
@@ -575,6 +584,15 @@ window.ManifestComponentsProcessor = {
575
584
  if (script.async) scriptEl.async = true;
576
585
  if (script.defer) scriptEl.defer = true;
577
586
  document.head.appendChild(scriptEl);
587
+ } else if (script.content && script.type.trim().toLowerCase() === 'module') {
588
+ // Once per page, like an external module
589
+ const seen = Array.from(document.head.querySelectorAll('script[data-component-module]'));
590
+ if (seen.some(s => s.textContent === script.content)) return;
591
+ const scriptEl = document.createElement('script');
592
+ scriptEl.setAttribute('data-component-module', name);
593
+ scriptEl.type = 'module';
594
+ scriptEl.textContent = script.content;
595
+ document.head.appendChild(scriptEl);
578
596
  } else if (script.content) {
579
597
  // Inline script → run in global scope
580
598
  try {
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.272';
5
+ const MANIFEST_BUILD_VERSION = '0.5.274';
6
6
 
7
7
  /* Manifest Data Sources - Configuration */
8
8
 
@@ -432,9 +432,12 @@ function sourceGeneration(dataSourceName) {
432
432
  // next read hydrates/fetches for the new scope; older loads are discarded
433
433
  function resetSource(dataSourceName) {
434
434
  sourceGen.set(dataSourceName, sourceGeneration(dataSourceName) + 1);
435
+ latestRead.delete(dataSourceName);
436
+ window.ManifestDataMain?.forgetLiveSource?.(dataSourceName);
435
437
  const prefix = `${dataSourceName}:`;
436
438
  for (const key of [...dataSourceCache.keys()]) if (key.startsWith(prefix)) dataSourceCache.delete(key);
437
439
  for (const key of [...loadingPromises.keys()]) if (key.startsWith(prefix)) loadingPromises.delete(key);
440
+ for (const key of [...loadReadTokens.keys()]) if (key.startsWith(prefix)) loadReadTokens.delete(key);
438
441
  freshness.delete(dataSourceName);
439
442
  pendingLocal.delete(dataSourceName);
440
443
  for (let i = pendingLandings.length - 1; i >= 0; i--) {
@@ -468,6 +471,15 @@ function setSourceState(dataSourceName, patch) {
468
471
  return next;
469
472
  }
470
473
 
474
+ // Last-issued read per source: a slower earlier read ($query or load) never lands over a later one
475
+ const latestRead = new Map();
476
+ let readSeq = 0;
477
+ const loadReadTokens = new Map(); // source:locale → token of the load in flight under that key
478
+ function beginRead(dataSourceName) { const token = ++readSeq; latestRead.set(dataSourceName, token); return token; }
479
+ function adoptRead(dataSourceName, token) { if (token !== undefined) latestRead.set(dataSourceName, token); }
480
+ // A reset clears the record: generation stamps already drop the older reads
481
+ function isLatestRead(dataSourceName, token) { return token === undefined || !latestRead.has(dataSourceName) || latestRead.get(dataSourceName) === token; }
482
+
471
483
  // Authoritative in-flight map: one promise per key, cleared on settle
472
484
  function runDeduped(key, factory) {
473
485
  if (loadingPromises.has(key)) return loadingPromises.get(key);
@@ -492,16 +504,74 @@ function ensureSourceArray(dataSourceName) {
492
504
  return store[dataSourceName];
493
505
  }
494
506
 
507
+ // Sort spec [[attr, 1|-1], ...] → comparator; nulls sort first ascending,
508
+ // strings case-insensitively (Appwrite's collation)
509
+ function compareBy(order) {
510
+ const norm = v => (typeof v === 'string' ? v.toLowerCase() : v);
511
+ return (a, b) => {
512
+ for (const [key, dir] of order) {
513
+ const x = norm(a?.[key]), y = norm(b?.[key]);
514
+ if (x === y) continue;
515
+ if (x === null || x === undefined) return -dir;
516
+ if (y === null || y === undefined) return dir;
517
+ return (x < y ? -1 : 1) * dir;
518
+ }
519
+ return 0;
520
+ };
521
+ }
522
+
523
+ function sortedIndex(arr, row, cmp) {
524
+ let lo = 0, hi = arr.length;
525
+ while (lo < hi) {
526
+ const mid = (lo + hi) >> 1;
527
+ if (cmp(arr[mid], row) <= 0) lo = mid + 1; else hi = mid;
528
+ }
529
+ return lo;
530
+ }
531
+
532
+ // Ordered append (realtime on a sorted source): new rows insert at their sort
533
+ // position; merged rows whose sort key moved are re-positioned. Structure is
534
+ // rebuilt on a raw copy and assigned once (one reactive write, not per splice)
535
+ function upsertOrdered(store, dataSourceName, rows, order) {
536
+ const cmp = compareBy(order);
537
+ const cur = store[dataSourceName];
538
+ const curRaw = rawOf(store)[dataSourceName];
539
+ let next = null;
540
+ for (const row of rows) {
541
+ const list = next || curRaw;
542
+ const id = rowKey(row);
543
+ const i = id !== null ? list.findIndex(r => rowKey(r) === id) : -1;
544
+ if (i !== -1) {
545
+ // Only a changed sort value moves a held row; the server's placement otherwise stands
546
+ const keyChanged = order.some(([key]) => Object.prototype.hasOwnProperty.call(row, key) && !sameScalarOrList(list[i][key], row[key]));
547
+ mergeRowFields(next ? (Alpine.reactive ? Alpine.reactive(list[i]) : list[i]) : cur[i], row, dataSourceName);
548
+ const misplaced = keyChanged && ((i > 0 && cmp(list[i - 1], list[i]) > 0) || (i < list.length - 1 && cmp(list[i], list[i + 1]) > 0));
549
+ if (!misplaced) continue;
550
+ if (!next) next = curRaw.slice();
551
+ const [moved] = next.splice(i, 1);
552
+ next.splice(sortedIndex(next, moved, cmp), 0, moved);
553
+ continue;
554
+ }
555
+ if (!next) next = curRaw.slice();
556
+ next.splice(sortedIndex(next, row, cmp), 0, rawOf(createReactiveReferences(row, dataSourceName)));
557
+ }
558
+ if (next) store[dataSourceName] = next;
559
+ }
560
+
495
561
  // Identity-preserving upsert by $id: existing rows are merged in place, new
496
562
  // rows created; append keeps the array, replace swaps it only when membership
497
563
  // or order changed
498
- function upsertRows(store, dataSourceName, rows, mode) {
564
+ function upsertRows(store, dataSourceName, rows, mode, order) {
499
565
  const raw = rawOf(store);
500
566
  const curRaw = Array.isArray(raw[dataSourceName]) ? raw[dataSourceName] : null;
501
567
  if (!curRaw) {
502
568
  store[dataSourceName] = rows.map(row => createReactiveReferences(row, dataSourceName));
503
569
  return;
504
570
  }
571
+ if (mode === 'append' && Array.isArray(order) && order.length) {
572
+ upsertOrdered(store, dataSourceName, rows, order);
573
+ return;
574
+ }
505
575
  const cur = store[dataSourceName];
506
576
  const index = new Map();
507
577
  curRaw.forEach((row, i) => { const id = rowKey(row); if (id !== null) index.set(id, i); });
@@ -557,7 +627,7 @@ function writeSource(dataSourceName, data, options = {}) {
557
627
  rawDataStore.set(dataSourceName, data);
558
628
  }
559
629
  } else if (Array.isArray(data)) {
560
- upsertRows(store, dataSourceName, data, mode);
630
+ upsertRows(store, dataSourceName, data, mode, options.order);
561
631
  const arr = rawOf(store)[dataSourceName];
562
632
  rawDataStore.set(dataSourceName, arr);
563
633
  attachMethods(arr, dataSourceName);
@@ -1523,6 +1593,10 @@ window.ManifestDataStore = {
1523
1593
  // Stale-first + dedupe (§11.1)
1524
1594
  setSourceState,
1525
1595
  runDeduped,
1596
+ beginRead,
1597
+ adoptRead,
1598
+ loadReadTokens,
1599
+ isLatestRead,
1526
1600
  sourceFreshness,
1527
1601
  // Persistence (§12.2)
1528
1602
  sourceGeneration,
@@ -2865,7 +2939,8 @@ window.ManifestDataLoaders = {
2865
2939
  // Load from API endpoint (read-only)
2866
2940
  async function loadFromAPI(dataSource) {
2867
2941
  try {
2868
- const url = new URL(window.ManifestDataConfig.interpolateEnvVars(dataSource.url));
2942
+ // Relative urls resolve against the page
2943
+ const url = new URL(window.ManifestDataConfig.interpolateEnvVars(dataSource.url), window.location.href);
2869
2944
 
2870
2945
  // Add query parameters
2871
2946
  if (dataSource.params) {
@@ -3176,7 +3251,15 @@ async function executeMutation(mutationConfig) {
3176
3251
  if (type === 'create') {
3177
3252
  // Ack: swap the temp row for the server row in place (new $id → new $files binding)
3178
3253
  const { store, arr, index } = findRawIndex(dataSourceName, optimisticData.$id);
3179
- if (store && arr) {
3254
+ // Realtime echo of this create may have landed first under the real $id
3255
+ const held = (arr && result.$id !== optimisticData.$id) ? arr.findIndex(r => r && r.$id === result.$id) : -1;
3256
+ if (store && arr && held !== -1) {
3257
+ const ds = window.ManifestDataStore;
3258
+ ds.mergeRowFields(store[dataSourceName][held], result, dataSourceName);
3259
+ if (index !== -1) store[dataSourceName].splice(index, 1);
3260
+ ds.touchSource(dataSourceName);
3261
+ clearSourceCaches(dataSourceName);
3262
+ } else if (store && arr) {
3180
3263
  if (index !== -1) {
3181
3264
  const ds = window.ManifestDataStore;
3182
3265
  store[dataSourceName].splice(index, 1, ds.createReactiveReferences(result, dataSourceName));
@@ -4602,52 +4685,9 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
4602
4685
  });
4603
4686
  }
4604
4687
 
4605
- // Attach pagination methods
4606
- const createPaginationMethod = (methodName) => {
4607
- return async function (...args) {
4608
- const manifest = await window.ManifestDataConfig.ensureManifest();
4609
- if (!manifest?.data) {
4610
- throw new Error('[Manifest Data] Manifest not available');
4611
- }
4612
- const dataSource = manifest.data[dataSourceName];
4613
- if (!dataSource || !window.ManifestDataConfig.isAppwriteCollection(dataSource)) {
4614
- throw new Error(`[Manifest Data] Pagination is only supported for Appwrite data sources`);
4615
- }
4616
- const scope = window.ManifestDataConfig.getScope(dataSource);
4617
- const scopeColumns = window.ManifestDataConfig.getScopeColumns(dataSource);
4618
- const queriesConfig = window.ManifestDataConfig.getQueries(dataSource);
4619
- const baseQueries = queriesConfig
4620
- ? await window.ManifestDataQueries.buildAppwriteQueries(queriesConfig.default || queriesConfig, scope, scopeColumns)
4621
- : await window.ManifestDataQueries.buildAppwriteQueries([], scope, scopeColumns);
4622
-
4623
- if (baseQueries === null) {
4624
- throw new Error(`[Manifest Data] "${dataSourceName}" pagination: auth not ready yet — retry after auth settles`);
4625
- }
4626
-
4627
- if (methodName === '$first') {
4628
- const limit = args[0] || 10;
4629
- return await window.ManifestDataPagination.getFirstPage(dataSourceName, limit, baseQueries);
4630
- } else if (methodName === '$next') {
4631
- const [cursor, limit = 10] = args;
4632
- if (!cursor) {
4633
- throw new Error('[Manifest Data] Cursor is required for $next');
4634
- }
4635
- return await window.ManifestDataPagination.getNextPage(dataSourceName, cursor, limit, baseQueries);
4636
- } else if (methodName === '$prev') {
4637
- const [cursor, limit = 10] = args;
4638
- if (!cursor) {
4639
- throw new Error('[Manifest Data] Cursor is required for $prev');
4640
- }
4641
- return await window.ManifestDataPagination.getPrevPage(dataSourceName, cursor, limit, baseQueries);
4642
- } else if (methodName === '$page') {
4643
- const [pageNumber, limit = 10] = args;
4644
- if (!pageNumber || pageNumber < 1) {
4645
- throw new Error('[Manifest Data] Page number must be >= 1');
4646
- }
4647
- return await window.ManifestDataPagination.getPage(dataSourceName, pageNumber, limit, baseQueries);
4648
- }
4649
- };
4650
- };
4688
+ // Attach pagination methods (one implementation: magic.pagination)
4689
+ const createPaginationMethod = (methodName) => (...args) =>
4690
+ window.ManifestDataProxiesMagic.createPaginationMethod(methodName, dataSourceName)(...args);
4651
4691
 
4652
4692
  ['$first', '$next', '$prev', '$page'].forEach(methodName => {
4653
4693
  Object.defineProperty(array, methodName, {
@@ -4952,52 +4992,7 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
4952
4992
 
4953
4993
  // Handle pagination methods
4954
4994
  if (key === '$first' || key === '$next' || key === '$prev' || key === '$page') {
4955
- return async function (...args) {
4956
- const manifest = await window.ManifestDataConfig.ensureManifest();
4957
- if (!manifest?.data) {
4958
- throw new Error('[Manifest Data] Manifest not available');
4959
- }
4960
-
4961
- const dataSource = manifest.data[dataSourceName];
4962
- if (!dataSource || !window.ManifestDataConfig.isAppwriteCollection(dataSource)) {
4963
- throw new Error(`[Manifest Data] Pagination is only supported for Appwrite data sources`);
4964
- }
4965
-
4966
- // Get base queries (from manifest or scope)
4967
- const scope = window.ManifestDataConfig.getScope(dataSource);
4968
- const scopeColumns = window.ManifestDataConfig.getScopeColumns(dataSource);
4969
- const queriesConfig = window.ManifestDataConfig.getQueries(dataSource);
4970
- const baseQueries = queriesConfig
4971
- ? await window.ManifestDataQueries.buildAppwriteQueries(queriesConfig.default || queriesConfig, scope, scopeColumns)
4972
- : await window.ManifestDataQueries.buildAppwriteQueries([], scope, scopeColumns);
4973
-
4974
- if (baseQueries === null) {
4975
- throw new Error(`[Manifest Data] "${dataSourceName}" pagination: auth not ready yet — retry after auth settles`);
4976
- }
4977
-
4978
- if (key === '$first') {
4979
- const limit = args[0] || 10;
4980
- return await window.ManifestDataPagination.getFirstPage(dataSourceName, limit, baseQueries);
4981
- } else if (key === '$next') {
4982
- const [cursor, limit = 10] = args;
4983
- if (!cursor) {
4984
- throw new Error('[Manifest Data] Cursor is required for $next');
4985
- }
4986
- return await window.ManifestDataPagination.getNextPage(dataSourceName, cursor, limit, baseQueries);
4987
- } else if (key === '$prev') {
4988
- const [cursor, limit = 10] = args;
4989
- if (!cursor) {
4990
- throw new Error('[Manifest Data] Cursor is required for $prev');
4991
- }
4992
- return await window.ManifestDataPagination.getPrevPage(dataSourceName, cursor, limit, baseQueries);
4993
- } else if (key === '$page') {
4994
- const [pageNumber, limit = 10] = args;
4995
- if (!pageNumber || pageNumber < 1) {
4996
- throw new Error('[Manifest Data] Page number must be >= 1');
4997
- }
4998
- return await window.ManifestDataPagination.getPage(dataSourceName, pageNumber, limit, baseQueries);
4999
- }
5000
- };
4995
+ return (...args) => window.ManifestDataProxiesMagic.createPaginationMethod(key, dataSourceName)(...args);
5001
4996
  }
5002
4997
 
5003
4998
  // $files handler for tables
@@ -7701,6 +7696,8 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
7701
7696
  const handleMethod = async function (method, ...args) {
7702
7697
  const sourceUnloaded = args[3]?.sourceUnloaded === true;
7703
7698
  if (!sourceUnloaded) clearErrorState();
7699
+ // Reads are ordered by call, not by network start: the last-issued one lands
7700
+ const readToken = method === '$query' ? window.ManifestDataStore?.beginRead?.(dataSourceName) : undefined;
7704
7701
 
7705
7702
  try {
7706
7703
  const manifest = await window.ManifestDataConfig?.ensureManifest?.();
@@ -7793,13 +7790,19 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
7793
7790
 
7794
7791
  // Support batch updates
7795
7792
  if (Array.isArray(idOrArray)) {
7796
- const updates = idOrArray;
7793
+ // Batch: ids (or rows) sharing `data`, or [{ id, data }] entries
7794
+ const updates = idOrArray.map(update => (update !== null && typeof update === 'object')
7795
+ ? { id: update.$id || update.id || update.rowId, data: update.data || data || update }
7796
+ : { id: update, data });
7797
+ if (updates.some(u => u.id === undefined || u.id === null || u.id === '' || u.data === undefined)) {
7798
+ throw new Error(`[Manifest Data] "${dataSourceName}" $update: batch entries need an id and data`);
7799
+ }
7797
7800
  if (executeMutation) {
7798
7801
  // Use optimistic updates for batch
7799
7802
  const results = await Promise.all(
7800
7803
  updates.map(update => {
7801
- const entryId = update.id || update.$id || update.rowId;
7802
- const updateData = update.data || update;
7804
+ const entryId = update.id;
7805
+ const updateData = update.data;
7803
7806
  return executeMutation({
7804
7807
  type: 'update',
7805
7808
  dataSourceName,
@@ -7829,8 +7832,8 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
7829
7832
  window.ManifestDataAppwrite.updateRow(
7830
7833
  appwriteConfig.databaseId,
7831
7834
  tableId,
7832
- update.id || update.$id || update.rowId,
7833
- update.data || update
7835
+ update.id,
7836
+ update.data
7834
7837
  )
7835
7838
  )
7836
7839
  );
@@ -8077,23 +8080,24 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
8077
8080
  }
8078
8081
  // Dedupe key: source + serialized queries (never across different queries)
8079
8082
  const key = `${dataSourceName}:$query:${JSON.stringify(appwriteQueries.map(q => String(q)))}`;
8080
- const { runDeduped, landRows, sourceGeneration } = window.ManifestDataStore || {};
8083
+ const { runDeduped, landRows, sourceGeneration, isLatestRead } = window.ManifestDataStore || {};
8081
8084
  const generation = sourceGeneration?.(dataSourceName);
8082
8085
  const requireAuth = window.ManifestDataMain?.requiresIdentity?.({ ...dataSource, queries: [dataSource.queries, queries] }) ?? true;
8083
- const run = async () => {
8084
- const result = await window.ManifestDataAppwrite.loadTableRows(
8085
- appwriteConfig.databaseId,
8086
- tableId,
8087
- appwriteQueries,
8088
- { requireAuth }
8089
- );
8090
- // Network landing: query result replaces the source (coalesced, identity-preserving)
8091
- if (result !== undefined && landRows) {
8092
- await landRows(dataSourceName, result, { mode: 'replace', fresh: true, generation });
8093
- }
8094
- return result;
8095
- };
8096
- return runDeduped ? await runDeduped(key, run) : await run();
8086
+ const run = () => window.ManifestDataAppwrite.loadTableRows(
8087
+ appwriteConfig.databaseId,
8088
+ tableId,
8089
+ appwriteQueries,
8090
+ { requireAuth }
8091
+ );
8092
+ const result = runDeduped ? await runDeduped(key, run) : await run();
8093
+ // Network landing: query result replaces the source (coalesced, identity-preserving);
8094
+ // only the last-issued read of this source lands
8095
+ const latest = !isLatestRead || isLatestRead(dataSourceName, readToken);
8096
+ if (result !== undefined && landRows && latest && sourceGeneration?.(dataSourceName) === generation) {
8097
+ window.ManifestDataMain?.setActiveQuery?.(dataSourceName, queries || [], Array.isArray(result) ? result.length : 0);
8098
+ await landRows(dataSourceName, result, { mode: 'replace', fresh: true, generation });
8099
+ }
8100
+ return result;
8097
8101
  }
8098
8102
  }
8099
8103
 
@@ -9198,6 +9202,8 @@ window.ManifestDataProxiesMagic.createUploadMethod = createUploadMethod;
9198
9202
  /* Manifest Data Sources - Magic Method Pagination Handlers */
9199
9203
  // $first, $next, $prev, $page
9200
9204
 
9205
+ const PAGE_WINDOW_METHODS = new Set(['limit', 'offset', 'cursorAfter', 'cursorBefore']);
9206
+
9201
9207
  function createPaginationMethod(methodName, dataSourceName) {
9202
9208
  return async function (...args) {
9203
9209
  const manifest = await window.ManifestDataConfig.ensureManifest();
@@ -9213,10 +9219,13 @@ function createPaginationMethod(methodName, dataSourceName) {
9213
9219
  // Base queries from manifest or scope
9214
9220
  const scope = window.ManifestDataConfig.getScope(dataSource);
9215
9221
  const scopeColumns = window.ManifestDataConfig.getScopeColumns(dataSource);
9222
+ // Pages follow the active $query, else the default queries; paging supplies its own window
9216
9223
  const queriesConfig = window.ManifestDataConfig.getQueries(dataSource);
9217
- const baseQueries = queriesConfig
9218
- ? await window.ManifestDataQueries.buildAppwriteQueries(queriesConfig.default || queriesConfig, scope, scopeColumns)
9219
- : await window.ManifestDataQueries.buildAppwriteQueries([], scope, scopeColumns);
9224
+ const defaults = queriesConfig ? (queriesConfig.default || queriesConfig) : [];
9225
+ const active = window.ManifestDataMain?.getActiveQuery?.(dataSourceName);
9226
+ const filters = (Array.isArray(active) ? active : (Array.isArray(defaults) ? defaults : []))
9227
+ .filter(q => !(Array.isArray(q) && PAGE_WINDOW_METHODS.has(q[0])));
9228
+ const baseQueries = await window.ManifestDataQueries.buildAppwriteQueries(filters, scope, scopeColumns);
9220
9229
 
9221
9230
  if (baseQueries === null) {
9222
9231
  throw new Error(`[Manifest Data] "${dataSourceName}" pagination: auth not ready yet — retry after auth settles`);
@@ -11905,6 +11914,56 @@ function configureRealtimeMeta(dataSourceName, config, initialCount, limitQuery)
11905
11914
  window.ManifestDataStore.setSourceState(dataSourceName, { realtimeOverflow: 0 });
11906
11915
  }
11907
11916
 
11917
+ // Active $query per source (manifest-format): realtime matches, orders and
11918
+ // server-filters by it until the next default load replaces the rows
11919
+ const activeQueries = new Map();
11920
+ const realtimeBinds = new Map(); // Map<name, { config, mode, rebind }>
11921
+
11922
+ function realtimeQueriesFor(dataSourceName, defaults) {
11923
+ return activeQueries.has(dataSourceName) ? activeQueries.get(dataSourceName) : defaults;
11924
+ }
11925
+
11926
+ // [['orderDesc', 'n'], ...] → [['n', -1], ...]; orderRandom has no stable position
11927
+ function sortSpec(queries) {
11928
+ if (!Array.isArray(queries)) return null;
11929
+ if (queries.some(q => Array.isArray(q) && q[0] === 'orderRandom')) return null;
11930
+ const order = queries
11931
+ .filter(q => Array.isArray(q) && (q[0] === 'orderAsc' || q[0] === 'orderDesc') && typeof q[1] === 'string')
11932
+ .map(q => [q[1], q[0] === 'orderAsc' ? 1 : -1]);
11933
+ return order.length ? order : null;
11934
+ }
11935
+
11936
+ function rebindLive(dataSourceName) {
11937
+ const bind = realtimeBinds.get(dataSourceName);
11938
+ if (bind?.mode !== 'all') return;
11939
+ Promise.resolve().then(bind.rebind).catch(error => console.error(`[Manifest Data] Realtime rebind failed for "${dataSourceName}":`, error));
11940
+ }
11941
+
11942
+ // $query landing: realtime follows the new filter set (caps reset to its result)
11943
+ function setActiveQuery(dataSourceName, queries, rowCount) {
11944
+ const list = Array.isArray(queries) ? queries : [];
11945
+ activeQueries.set(dataSourceName, list);
11946
+ const bind = realtimeBinds.get(dataSourceName);
11947
+ if (!bind) return;
11948
+ const limitQ = list.find(q => Array.isArray(q) && q[0] === 'limit');
11949
+ configureRealtimeMeta(dataSourceName, bind.config, rowCount || 0, limitQ ? limitQ[1] : null);
11950
+ rebindLive(dataSourceName);
11951
+ }
11952
+
11953
+ // Default rows landed: back to the manifest queries (caps sized to those rows)
11954
+ function clearActiveQuery(dataSourceName, rowCount) {
11955
+ if (!activeQueries.delete(dataSourceName)) return;
11956
+ const bind = realtimeBinds.get(dataSourceName);
11957
+ if (bind) configureRealtimeMeta(dataSourceName, bind.config, rowCount || 0, bind.defaultLimit);
11958
+ rebindLive(dataSourceName);
11959
+ }
11960
+
11961
+ // Source reset (identity/scope) or torn down: nothing of the old binding carries over
11962
+ function forgetLiveSource(dataSourceName) {
11963
+ activeQueries.delete(dataSourceName);
11964
+ realtimeBinds.delete(dataSourceName);
11965
+ }
11966
+
11908
11967
  // Events not applied since the last load — $x.<source>.$realtimeOverflow
11909
11968
  function bumpRealtimeOverflow(dataSourceName) {
11910
11969
  const state = Alpine.store('data')?.[`_${dataSourceName}_state`];
@@ -11913,12 +11972,12 @@ function bumpRealtimeOverflow(dataSourceName) {
11913
11972
 
11914
11973
  // Append under the cap. 'window' evicts the oldest realtime-appended row first
11915
11974
  // (never an initially-loaded row); otherwise the event counts in $realtimeOverflow.
11916
- function realtimeAppend(dataSourceName, row, currentRows, generation) {
11975
+ function realtimeAppend(dataSourceName, row, currentRows, generation, order) {
11917
11976
  const { landRows, landRemove } = window.ManifestDataStore;
11918
11977
  const meta = realtimeMeta.get(dataSourceName);
11919
11978
  if (!meta || meta.maxRows === 0) {
11920
11979
  if (meta) meta.appended.push(row.$id);
11921
- landRows(dataSourceName, [row], { mode: 'append', generation });
11980
+ landRows(dataSourceName, [row], { mode: 'append', generation, order });
11922
11981
  return;
11923
11982
  }
11924
11983
  const len = currentRows.length + meta.pending;
@@ -11943,7 +12002,7 @@ function realtimeAppend(dataSourceName, row, currentRows, generation) {
11943
12002
  }
11944
12003
  meta.pending++;
11945
12004
  meta.appended.push(row.$id);
11946
- landRows(dataSourceName, [row], { mode: 'append', generation })
12005
+ landRows(dataSourceName, [row], { mode: 'append', generation, order })
11947
12006
  .then(() => { meta.pending = Math.max(0, meta.pending - 1); });
11948
12007
  }
11949
12008
 
@@ -11992,6 +12051,7 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
11992
12051
 
11993
12052
  // Network landings: coalesced per frame (landRows upserts by $id), never the sync mutation path
11994
12053
  const { landRows, landRemove } = window.ManifestDataStore;
12054
+ const order = sortSpec(queriesConfig);
11995
12055
 
11996
12056
  if (eventType === 'create') {
11997
12057
  const row = payload?.$id ? payload : (payload?.row || payload);
@@ -12001,9 +12061,9 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
12001
12061
  if (rowMatchesScope && eventMatchesQueries(row, queriesConfig)) {
12002
12062
  if (currentRows.find(r => r.$id === row.$id)) {
12003
12063
  // Echo of a row already held (optimistic create): merge, never counts as growth
12004
- landRows(dataSourceName, [row], { mode: 'append', generation });
12064
+ landRows(dataSourceName, [row], { mode: 'append', generation, order });
12005
12065
  } else {
12006
- realtimeAppend(dataSourceName, row, currentRows, generation);
12066
+ realtimeAppend(dataSourceName, row, currentRows, generation, order);
12007
12067
  }
12008
12068
  }
12009
12069
  } else {
@@ -12024,7 +12084,7 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
12024
12084
  }
12025
12085
  if (!existingRow) {
12026
12086
  // Row not in list, but matches now - add it (capped like a create)
12027
- realtimeAppend(dataSourceName, row, currentRows, generation);
12087
+ realtimeAppend(dataSourceName, row, currentRows, generation, order);
12028
12088
  return;
12029
12089
  }
12030
12090
 
@@ -12084,7 +12144,7 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
12084
12144
  const fileIdsChanged = isProjectWithFiles &&
12085
12145
  JSON.stringify(existingFileIds) !== JSON.stringify(incomingFileIds);
12086
12146
 
12087
- landRows(dataSourceName, [row], { mode: 'append', generation });
12147
+ landRows(dataSourceName, [row], { mode: 'append', generation, order });
12088
12148
 
12089
12149
  // Emit custom event for project file updates so UI can refresh
12090
12150
  if (fileIdsChanged && dataSourceName === 'projects') {
@@ -12264,11 +12324,17 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
12264
12324
  return cachedData;
12265
12325
  }
12266
12326
 
12267
- // In flight for this source+locale: share it (every entry path lands here)
12327
+ // In flight for this source+locale: share it (every entry path lands here);
12328
+ // joining makes it the latest read again
12268
12329
  if (loadingPromises.has(cacheKey)) {
12330
+ window.ManifestDataStore.adoptRead?.(dataSourceName, window.ManifestDataStore.loadReadTokens?.get(cacheKey));
12269
12331
  return loadingPromises.get(cacheKey);
12270
12332
  }
12271
12333
 
12334
+ // Reads are ordered by call: a $query issued after this load wins over it
12335
+ const readToken = window.ManifestDataStore.beginRead?.(dataSourceName);
12336
+ window.ManifestDataStore.loadReadTokens?.set(cacheKey, readToken);
12337
+
12272
12338
  // Loading state: rows already landed stay live; first-ever load clears
12273
12339
  const keepRows = hasLiveRows(dataSourceName);
12274
12340
  if (!isInitializing) {
@@ -12403,22 +12469,29 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
12403
12469
  if (dataSource.realtime !== false && window.ManifestDataRealtime && window.ManifestDataRealtime.subscribeToTable) {
12404
12470
  const rtQueries = Array.isArray(queriesConfig?.default || queriesConfig) ? (queriesConfig.default || queriesConfig) : [];
12405
12471
  const limitQ = rtQueries.find(q => Array.isArray(q) && q[0] === 'limit');
12406
- configureRealtimeMeta(dataSourceName, dataSource.realtime, Array.isArray(data) ? data.length : 0, limitQ ? limitQ[1] : null);
12472
+ if (!activeQueries.has(dataSourceName) || !realtimeMeta.has(dataSourceName)) {
12473
+ configureRealtimeMeta(dataSourceName, dataSource.realtime, Array.isArray(data) ? data.length : 0, limitQ ? limitQ[1] : null);
12474
+ }
12407
12475
  // Events server-filtered where the SDK supports realtime queries; the client
12408
12476
  // matcher below still runs. serverFilter: 'scope' (default) | 'all' (alias true) | false
12409
12477
  const rtMode = serverFilterMode(dataSource.realtime);
12410
- const rtFilterSpec = rtMode ? { queriesConfig: rtQueries, scope, scopeColumns, mode: rtMode } : null;
12411
- const unsubscribe = await window.ManifestDataRealtime.subscribeToTable(
12478
+ const rtFilterSpec = rtMode ? {
12479
+ get queriesConfig() { return realtimeQueriesFor(dataSourceName, rtQueries); },
12480
+ scope, scopeColumns, mode: rtMode
12481
+ } : null;
12482
+ const subscribe = () => window.ManifestDataRealtime.subscribeToTable(
12412
12483
  dataSourceName,
12413
12484
  appwriteConfig.databaseId,
12414
12485
  tableId,
12415
12486
  scope,
12416
12487
  async (eventType, payload) => {
12417
- await handleTableRealtimeEvent(dataSourceName, appwriteConfig.databaseId, tableId, scope, scopeColumns, eventType, payload, generation, rtQueries);
12488
+ await handleTableRealtimeEvent(dataSourceName, appwriteConfig.databaseId, tableId, scope, scopeColumns, eventType, payload, generation, realtimeQueriesFor(dataSourceName, rtQueries));
12418
12489
  },
12419
12490
  () => !superseded(),
12420
12491
  rtFilterSpec
12421
12492
  );
12493
+ realtimeBinds.set(dataSourceName, { config: dataSource.realtime, mode: rtMode, rebind: subscribe, defaultLimit: limitQ ? limitQ[1] : null });
12494
+ const unsubscribe = await subscribe();
12422
12495
  if (superseded()) { dropSubscription(dataSourceName, unsubscribe); landed = true; return null; }
12423
12496
  }
12424
12497
  } else if (bucketId) {
@@ -12594,6 +12667,13 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
12594
12667
 
12595
12668
  if (superseded()) { landed = true; return null; }
12596
12669
 
12670
+ // A $query issued since this load began: its rows stand, this result never lands
12671
+ if (window.ManifestDataStore.isLatestRead && !window.ManifestDataStore.isLatestRead(dataSourceName, readToken)) {
12672
+ landed = true;
12673
+ if (!isInitializing) setSourceState(dataSourceName, { loading: false });
12674
+ return enhancedData;
12675
+ }
12676
+
12597
12677
  // Cache is always safe — the key carries this load's locale.
12598
12678
  dataSourceCache.set(cacheKey, enhancedData);
12599
12679
 
@@ -12610,6 +12690,7 @@ async function loadDataSource(dataSourceName, locale = 'en', options = {}) {
12610
12690
  // Network landing (coalesced per frame, merges by $id); resolves once visible
12611
12691
  if (!isInitializing && !staleLocale) {
12612
12692
  landed = true;
12693
+ clearActiveQuery(dataSourceName, Array.isArray(enhancedData) ? enhancedData.length : 0);
12613
12694
  await landRows(dataSourceName, enhancedData, { mode: 'replace', loading: false, error: null, ready: true, fresh: true, generation });
12614
12695
  }
12615
12696
 
@@ -12780,12 +12861,38 @@ async function initializeDataSourcesPlugin() {
12780
12861
  }
12781
12862
 
12782
12863
  // Export loadDataSource for use by team change listener
12864
+ // Catch-up: events missed while offline or asleep never arrive, so live sources
12865
+ // refetch (their active $query, else the default load) on reconnect or wake
12866
+ const CATCH_UP_HIDDEN_MS = 60000;
12867
+ let hiddenSince = document.hidden ? Date.now() : null;
12868
+ let catchUpTimer = null;
12869
+ const catchUpLiveSources = () => {
12870
+ clearTimeout(catchUpTimer);
12871
+ catchUpTimer = setTimeout(() => {
12872
+ for (const name of realtimeBinds.keys()) {
12873
+ const active = activeQueries.get(name);
12874
+ const handler = active && window.ManifestDataProxiesAppwrite?.createAppwriteMethodsHandler?.(name, reloadDataSource);
12875
+ Promise.resolve(handler ? handler('$query', active) : reloadDataSource(name)).catch(() => { /* $error carries it */ });
12876
+ }
12877
+ }, 250);
12878
+ };
12879
+ window.addEventListener('online', catchUpLiveSources);
12880
+ document.addEventListener('visibilitychange', () => {
12881
+ if (document.hidden) { hiddenSince = Date.now(); return; }
12882
+ const away = hiddenSince !== null ? Date.now() - hiddenSince : 0;
12883
+ hiddenSince = null;
12884
+ if (away >= CATCH_UP_HIDDEN_MS) catchUpLiveSources();
12885
+ });
12886
+
12783
12887
  window.ManifestDataMain = {
12784
12888
  loadDataSource,
12785
12889
  reloadDataSource,
12786
12890
  _loadDataSource: reloadDataSource, // Internal: every user is a reload-after-mutation
12787
12891
  filterFilesByScope, // Export for use by getFilesForEntry
12788
- requiresIdentity
12892
+ requiresIdentity,
12893
+ setActiveQuery,
12894
+ forgetLiveSource,
12895
+ getActiveQuery: (dataSourceName) => activeQueries.get(dataSourceName) || null
12789
12896
  };
12790
12897
 
12791
12898
  // Listen for manifest:dev-reload (fired by mnfst-run when a data file changes).
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest.appwrite.auth.js": "sha384-gWLSeVPqCLMiUZB8MU7GqMg3x2yULFUsp8nTTZrPp/9Dlkix42rBn9yIimvwrHqZ",
3
- "manifest.appwrite.data.js": "sha384-YQPP4xNQPfWNvlQvdZQoMLAxTIuKHIc66unLDap7yCs3UzftmFNm3KqbRdaVU803",
3
+ "manifest.appwrite.data.js": "sha384-7gDP0QYT/sjPnPxKB0DahCW7wGTjVeKzqRHJSkqv4R4J8CtcmB7b6qTEsyK4iGoh",
4
4
  "manifest.appwrite.presences.js": "sha384-LINPhIPTCK+rA8zd+HPUzMSEgH6Sl2vQMEyItT97bnEE2FYGYKqjXX+CWdt6nl9z",
5
5
  "manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
6
6
  "manifest.charts.js": "sha384-AFbP7mQnigy7DU1s+YE30yKTeQAKfgNazhbfSNbfd9ZpLgeulZKaPi/fAq3gJHDO",
@@ -9,9 +9,9 @@
9
9
  "manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
10
10
  "manifest.colorpicker.js": "sha384-P+Zx0QzhMGfku8s3yEskTte4/iXCGVX3zdhu42IGNfFcSmhOW+nqVK7w+uczWRGY",
11
11
  "manifest.combobox.js": "sha384-/lJo6vaxKjgnBSRs/dzkhK1dzGk3GBw8FISoNJZB70mwAVwu1k5veZyBXjOb4keS",
12
- "manifest.components.js": "sha384-BUFA8h+G5Plr82sV7bKzO42pPoiRZwRQcKj1lt2ROFiKjjlkayU/vmkAFqEsdBA+",
12
+ "manifest.components.js": "sha384-1el5WMdgmzu/2ocaYN21OjLi/lfjllvA8aju1J3knYuOizw+XFfJU1+rvGqYW7qk",
13
13
  "manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
14
- "manifest.data.js": "sha384-O11MgHUpEHe1mDeCeCD17a4IfQeHN5WSDrxZ8KX6lDNxmcIc1r1aWLHR7qifrWsQ",
14
+ "manifest.data.js": "sha384-mV2B+tijlZgcD/MFDLzw6w+6fGd0rZpGTTOJEyv6LJYwRydD3XtekFaMvZCWzudH",
15
15
  "manifest.datepicker.js": "sha384-EzlMwvIxlla/0BuyhAEqxN0M8TGcWIWgEIg840V0akKQQK1uY648wPWMkobvtq/o",
16
16
  "manifest.defer.js": "sha384-PRp7+dCZHnnl55rX2xrHutLrQnszHo7o5o9Tq28M/yjLZPuXDzcwQNxCdvbtCYSz",
17
17
  "manifest.device.js": "sha384-YeQaS56L8IILqrQNBSeAOLwIh5DuAOrE60cYLK1+46qJtUrXlAvqGe2j5mbV4Vq7",
@@ -29,7 +29,7 @@
29
29
  "manifest.status.js": "sha384-ofu7nBnVIGZ1U1o2R9d2H3V8aEMPUCVbl5FO3gIToXqi0oa36fTviEdh5UU7Kitw",
30
30
  "manifest.submissions.js": "sha384-v9eS47ytAfTGIKAyVnbd0QWegucVS7yduvigqfsxxoqXmbaEoy0M2y+BiI4xeZBd",
31
31
  "manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
32
- "manifest.sw.js": "sha384-3K4qd91F3pvjITZtDU0OnykqElXdfXyrrJ6LF9L5AN8X9xdEBVTfcuPYrXFsJRS8",
32
+ "manifest.sw.js": "sha384-tJoDnCGd8KNuPQSC2U45K3sFnVP+FKP/Qddzg4BoFsBjwhwQCk/FDo2i54aS/l+s",
33
33
  "manifest.tabs.js": "sha384-otU09H7jnawVKQdLAYpMCd3jx00tgyPVr42D2hLIBzN5dG4se505YesavdnMqEi1",
34
34
  "manifest.tailwind.js": "sha384-yuP3LEvWU+QVjxjkZ0YhErtC9RrGmhOmHjjFjhS9obqZbp5/KTIEnSR0CXDvHE0B",
35
35
  "manifest.toasts.js": "sha384-UpOz9fAdGeGUhL0NamBg5iPepEfIy73p9HYdcxfOQC5b7CVsl7Our0skdZzaLNcC",
@@ -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.272';
19
+ const BUILD_VERSION = '0.5.274';
20
20
  const CACHE_PREFIX = 'mnfst-sw:';
21
21
  const META_CACHE = CACHE_PREFIX + 'meta';
22
22
  // Sized to hold a full precache.json warm-up (PRECACHE_MAX_ENTRIES server-side)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.272",
3
+ "version": "0.5.274",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",
@@ -50,7 +50,9 @@
50
50
  "test:e2e:magic": "node tests/e2e/magic-one-arg.e2e.mjs",
51
51
  "test:e2e:markdown-safe": "node tests/e2e/markdown-safe.e2e.mjs",
52
52
  "test:e2e:upload": "node tests/e2e/upload-first-load.e2e.mjs",
53
+ "test:e2e:test-kit": "node tests/e2e/test-kit-journeys.e2e.mjs",
53
54
  "test:e2e:submissions": "node tests/e2e/submissions.e2e.mjs",
55
+ "test:e2e:modules": "node tests/e2e/modules.e2e.mjs",
54
56
  "lint": "echo 'No linting configured'",
55
57
  "release:docs": "node scripts/release-bump.mjs packages/docs && cd packages/docs && npm publish --auth-type=web",
56
58
  "release:check": "node scripts/release-check.mjs"