mnfst 0.5.162 → 0.5.164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -250,24 +250,18 @@ window.ManifestDataConfig = {
250
250
 
251
251
  /* Manifest Data Sources - Store Management */
252
252
 
253
- // Cache for loaded data sources (raw data, not in Alpine store to avoid double-proxying)
253
+ // Raw data, kept out of Alpine's store to avoid double-proxying (recursion)
254
254
  const dataSourceCache = new Map();
255
255
  const loadingPromises = new Map();
256
-
257
- // Store raw data separately from Alpine's reactive store
258
- // This prevents Alpine from proxying our data, which causes recursion when we proxy it
259
256
  const rawDataStore = new Map();
260
257
 
261
- // Track initialization state
262
258
  let isInitializing = false;
263
259
  let initializationComplete = false;
264
260
 
265
- // Render-ready: debounced timer used by checkAndDispatchRenderReady
266
261
  let _renderReadyTimer = null;
267
- const RENDER_READY_QUIET_MS = 150; // ms of quiet (no loading) before firing
262
+ const RENDER_READY_QUIET_MS = 150; // quiet time before firing render-ready
268
263
 
269
- // Deep seal an object to prevent Alpine from making it reactive
270
- // This prevents double-proxying which causes recursion errors
264
+ // Deep seal so Alpine won't proxy (double-proxying causes recursion)
271
265
  function deepSeal(obj) {
272
266
  if (obj === null || typeof obj !== 'object') {
273
267
  return obj;
@@ -284,12 +278,8 @@ function deepSeal(obj) {
284
278
  }
285
279
  }
286
280
  } else {
287
- // Iterate own enumerable keys via Object.keys instead of for…in +
288
- // .hasOwnProperty(). The latter throws "hasOwnProperty is not a
289
- // function" on any object that either lacks the Object prototype
290
- // (e.g. Object.create(null)) or has a column literally named
291
- // `hasOwnProperty` shadowing the prototype method — both of which
292
- // can happen with payloads from Appwrite / arbitrary backends.
281
+ // Object.keys, not for…in + .hasOwnProperty: backend payloads may lack
282
+ // the Object prototype or shadow hasOwnProperty with a column of that name.
293
283
  for (const key of Object.keys(obj)) {
294
284
  const value = obj[key];
295
285
  if (value !== null && typeof value === 'object') {
@@ -416,15 +406,8 @@ function createReactiveReferences(data, dataSourceName = null) {
416
406
  }
417
407
 
418
408
  if (typeof data === 'object') {
419
- // Create new object with new references for each property.
420
- // Iterate via Object.keys() (own enumerable, no prototype walk)
421
- // rather than for…in + .hasOwnProperty(). The latter pattern
422
- // throws "hasOwnProperty is not a function" on any payload that
423
- // either has a column literally named `hasOwnProperty` shadowing
424
- // the prototype, or lacks the Object prototype entirely
425
- // (Object.create(null), some SDK response shapes). This is the
426
- // hot path for every Appwrite mutation result and realtime event,
427
- // so it must be defensive about arbitrary backend payloads.
409
+ // Object.keys, not for…in + .hasOwnProperty (see deepSeal). Hot path for
410
+ // every Appwrite mutation result and realtime event.
428
411
  const newObj = {};
429
412
  for (const key of Object.keys(data)) {
430
413
  const value = data[key];
@@ -841,9 +824,7 @@ function setupTeamChangeListener() {
841
824
  }
842
825
  }
843
826
 
844
- // Dispatch manifest:render-ready when all tracked data sources have settled.
845
- // Uses a debounce so rapid sequential source completions coalesce into one event.
846
- // The render script listens for this event instead of polling internal store state.
827
+ // Dispatch manifest:render-ready once all sources settle (debounced to coalesce).
847
828
  function checkAndDispatchRenderReady() {
848
829
  if (_renderReadyTimer) {
849
830
  clearTimeout(_renderReadyTimer);
@@ -2828,23 +2809,9 @@ window.ManifestDataProxies.globalAccessCache = globalAccessCache;
2828
2809
 
2829
2810
 
2830
2811
  /* Manifest Data Sources - Circular Reference Handler */
2831
- // Handles detection and resolution of circular references in proxy property access
2832
- // This is critical for preventing infinite recursion when Alpine re-evaluates expressions
2833
-
2834
- /**
2835
- * Handles circular reference detection and resolution
2836
- * @param {Object} params - Handler parameters
2837
- * @param {Set} params.activeProps - Set of currently active property accesses
2838
- * @param {string} params.propKey - The property key being accessed
2839
- * @param {Object} params.rawTarget - Raw target object (not Alpine-wrapped)
2840
- * @param {Array} params.path - Path array to the current object
2841
- * @param {string} params.key - The key being accessed
2842
- * @param {string} params.fullPath - Full path string for logging
2843
- * @param {number} params.currentDepth - Current call depth
2844
- * @param {string} params.triggeredBy - What triggered this access ('Alpine', 'Proxy', etc.)
2845
- * @param {boolean} params.shouldLog - Whether to log debug information
2846
- * @returns {*} The resolved value or undefined to break the cycle
2847
- */
2812
+ // Breaks infinite recursion when Alpine re-evaluates an expression that
2813
+ // re-reads a property still being accessed. Returns the cached plain copy if
2814
+ // available, else undefined to break the cycle.
2848
2815
  function handleCircularReference({
2849
2816
  activeProps,
2850
2817
  propKey,
@@ -2857,18 +2824,16 @@ function handleCircularReference({
2857
2824
  shouldLog
2858
2825
  }) {
2859
2826
  if (!activeProps || !activeProps.has(propKey)) {
2860
- return null; // Not a circular reference, continue normal flow
2827
+ return null; // Not circular, continue normal flow
2861
2828
  }
2862
2829
 
2863
2830
  if (shouldLog) {
2864
2831
  console.warn(`[Proxy] ⚠️ CIRCULAR ${fullPath} | depth:${currentDepth} | triggered by:${triggeredBy} | This is likely Alpine re-evaluation`);
2865
2832
  }
2866
2833
 
2867
- // Property is already being accessed - this is likely Alpine re-evaluating the expression
2868
- // CRITICAL: For simple objects that are already being accessed, return the cached plain copy
2869
- // if it exists. This prevents infinite recursion by ensuring Alpine gets the same object instance.
2834
+ // Prop already in flight (Alpine re-evaluating): hand back the cached plain
2835
+ // copy so Alpine gets the same instance and doesn't recurse.
2870
2836
  try {
2871
- // First, try to get the value from rawTarget
2872
2837
  let current = rawTarget;
2873
2838
  let pathValid = true;
2874
2839
  const accessPath = path.length === 0 ? [key] : [...path, key];
@@ -2892,7 +2857,7 @@ function handleCircularReference({
2892
2857
  return current;
2893
2858
  }
2894
2859
 
2895
- // If it's a simple object, check if we have a cached plain copy
2860
+ // Simple object: return its cached plain copy if present
2896
2861
  if (!Array.isArray(current)) {
2897
2862
  let isSimpleObject = true;
2898
2863
  try {
@@ -2907,7 +2872,6 @@ function handleCircularReference({
2907
2872
  }
2908
2873
 
2909
2874
  if (isSimpleObject) {
2910
- // Check for cached plain copy first - this is critical to prevent recursion
2911
2875
  if (!window.ManifestDataProxiesCore.frozenPlainCopyCache) {
2912
2876
  window.ManifestDataProxiesCore.frozenPlainCopyCache = new WeakMap();
2913
2877
  }
@@ -2915,8 +2879,7 @@ function handleCircularReference({
2915
2879
  const cachedCopy = plainCopyCache.get(current);
2916
2880
 
2917
2881
  if (cachedCopy) {
2918
- // Return cached copy immediately - don't create a new one
2919
- // DON'T remove from activeProps here - let the normal flow handle it
2882
+ // Leave propKey in activeProps normal flow clears it
2920
2883
  return cachedCopy;
2921
2884
  }
2922
2885
  }
@@ -2948,14 +2911,10 @@ if (typeof window !== 'undefined') {
2948
2911
 
2949
2912
 
2950
2913
  /* Manifest Data Sources - Simple Object Handler */
2951
- // Handles detection and creation of plain copies for simple objects
2952
- // This prevents infinite recursion when Alpine wraps proxies and accesses nested properties
2914
+ // Plain copies of primitive-only objects break the proxy chain so Alpine
2915
+ // wrapping + nested access can't recurse infinitely.
2953
2916
 
2954
- /**
2955
- * Checks if an object is a "simple object" (contains only primitives, no nested objects/arrays)
2956
- * @param {*} value - The value to check
2957
- * @returns {boolean} True if the object is simple (only primitives)
2958
- */
2917
+ // True if value is an object of only primitives (no nested objects/arrays)
2959
2918
  function isSimpleObject(value) {
2960
2919
  if (!value || typeof value !== 'object' || Array.isArray(value) || value === null) {
2961
2920
  return false;
@@ -2973,19 +2932,8 @@ function isSimpleObject(value) {
2973
2932
  }
2974
2933
  }
2975
2934
 
2976
- /**
2977
- * Creates or retrieves a cached plain copy of a simple object
2978
- * Plain copies are NOT frozen - Alpine needs to access properties on them
2979
- * @param {Object} value - The simple object to copy
2980
- * @param {Object} params - Handler parameters
2981
- * @param {Set} params.activeProps - Set of currently active property accesses
2982
- * @param {string} params.propKey - The property key being accessed
2983
- * @param {Object} params.rawTarget - Raw target object
2984
- * @param {string} params.fullPath - Full path string for logging
2985
- * @param {Map} params.callDepthMap - Map tracking call depth
2986
- * @param {boolean} params.shouldLog - Whether to log debug information
2987
- * @returns {Object|null} The plain copy, or null if not a simple object or copy failed
2988
- */
2935
+ // Get/create a cached plain copy of a simple object. Not frozen — Alpine
2936
+ // reads properties off it. Returns null if not simple or copy failed.
2989
2937
  function createOrGetPlainCopy(value, {
2990
2938
  activeProps,
2991
2939
  propKey,
@@ -3004,62 +2952,41 @@ function createOrGetPlainCopy(value, {
3004
2952
  }
3005
2953
  const plainCopyCache = window.ManifestDataProxiesCore.frozenPlainCopyCache;
3006
2954
 
3007
- // Check for cached copy first - this is critical to prevent recursion
2955
+ // Cached copy = same instance, so Alpine won't re-evaluate (prevents recursion)
3008
2956
  let cachedCopy = plainCopyCache.get(value);
3009
2957
  if (cachedCopy) {
3010
- // Return cached plain copy - Alpine won't see it as "new"
3011
- // CRITICAL: Remove from activeProps since plain copy is plain object (won't trigger proxy getters)
3012
2958
  if (activeProps) {
3013
2959
  activeProps.delete(propKey);
3014
2960
  }
3015
- // Reset depth after returning plain copy
3016
2961
  if (callDepthMap && rawTarget) {
3017
2962
  callDepthMap.delete(rawTarget);
3018
2963
  }
3019
2964
  return cachedCopy;
3020
2965
  }
3021
2966
 
3022
- // Create new plain copy
3023
-
3024
2967
  const plainCopy = {};
3025
2968
  try {
3026
- // CRITICAL: Copy property values directly (they're already primitives for simple objects)
3027
- // Don't freeze nested values - just copy them as-is
3028
2969
  for (const prop in value) {
3029
2970
  plainCopy[prop] = value[prop];
3030
2971
  }
3031
2972
 
3032
- // Don't freeze the object - Alpine needs to access properties on it
3033
- // Instead, return a plain object copy which Alpine won't wrap in reactivity
3034
- // because it's a new object instance each time (cached by WeakMap)
3035
-
3036
- // Cache the plain copy for future accesses (same instance = Alpine won't re-evaluate)
3037
2973
  plainCopyCache.set(value, plainCopy);
3038
2974
 
3039
- // CRITICAL: Remove from activeProps since plain copy is plain object (won't trigger proxy getters)
3040
- // The plain copy breaks the proxy chain, so we don't need to track it in activeProps
3041
- // This prevents false circular reference detection when Alpine accesses properties on the plain copy
2975
+ // Plain copy breaks the proxy chain, so drop it from activeProps to
2976
+ // avoid false circular-reference detection on Alpine's follow-up reads.
3042
2977
  if (activeProps) {
3043
2978
  activeProps.delete(propKey);
3044
2979
  }
3045
- // Reset depth after returning plain copy
3046
2980
  if (callDepthMap && rawTarget) {
3047
2981
  callDepthMap.delete(rawTarget);
3048
2982
  }
3049
2983
  return plainCopy;
3050
2984
  } catch (e) {
3051
- // Return null to indicate failure - caller should fall through to proxy creation
3052
2985
  return null;
3053
2986
  }
3054
2987
  }
3055
2988
 
3056
- /**
3057
- * Handles simple object detection and plain copy creation for a value
3058
- * This is the main entry point for simple object handling
3059
- * @param {*} value - The value to check
3060
- * @param {Object} params - Handler parameters
3061
- * @returns {Object|null} The plain copy if simple object, null otherwise
3062
- */
2989
+ // Entry point: plain copy if value is a simple object, else null
3063
2990
  function handleSimpleObject(value, params) {
3064
2991
  if (Array.isArray(value)) {
3065
2992
  return null; // Arrays are not simple objects
@@ -3084,15 +3011,8 @@ if (typeof window !== 'undefined') {
3084
3011
 
3085
3012
 
3086
3013
  /* Manifest Data Sources - Proxy Helper Functions */
3087
- // Utility functions for proxy creation and data manipulation
3088
3014
 
3089
- /**
3090
- * Find an item in nested data structures by path key and segments
3091
- * @param {*} data - The data to search (array or object)
3092
- * @param {string} pathKey - The key that contains the path value
3093
- * @param {Array} pathSegments - Array of path segments to match
3094
- * @returns {*} The found item or null
3095
- */
3015
+ // Find a nested item whose pathKey value matches one of pathSegments
3096
3016
  function findItemByPath(data, pathKey, pathSegments) {
3097
3017
  if (!pathSegments || pathSegments.length === 0) {
3098
3018
  return null;
@@ -3145,12 +3065,7 @@ function findItemByPath(data, pathKey, pathSegments) {
3145
3065
  return null;
3146
3066
  }
3147
3067
 
3148
- /**
3149
- * Find the group that contains a specific item
3150
- * @param {*} data - The data to search
3151
- * @param {*} targetItem - The item to find
3152
- * @returns {*} The group containing the item or null
3153
- */
3068
+ // Find the group (item with .group + .items array) that contains targetItem
3154
3069
  function findGroupContainingItem(data, targetItem) {
3155
3070
  if (Array.isArray(data)) {
3156
3071
  for (const item of data) {
@@ -3178,11 +3093,7 @@ function findGroupContainingItem(data, targetItem) {
3178
3093
  return null;
3179
3094
  }
3180
3095
 
3181
- /**
3182
- * Convert Alpine proxy to real array
3183
- * @param {*} proxyData - The proxy data to convert
3184
- * @returns {Array} The converted array or original value
3185
- */
3096
+ // Convert an Alpine proxy (or array-like) to a real array
3186
3097
  function convertProxyToArray(proxyData) {
3187
3098
  if (Array.isArray(proxyData)) {
3188
3099
  return proxyData;
@@ -3240,8 +3151,7 @@ function clearArrayProxyCacheForDataSource(dataSourceName) {
3240
3151
  // Track which arrays have methods attached to avoid re-attaching
3241
3152
  const arraysWithMethodsAttached = new WeakSet();
3242
3153
 
3243
- // Attach methods directly to an array (no proxy wrapper)
3244
- // This allows Alpine to track the array directly for reactivity
3154
+ // Attach methods directly to the array (no proxy wrapper) so Alpine tracks it directly
3245
3155
  function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3246
3156
  // Skip if already has methods attached
3247
3157
  if (arraysWithMethodsAttached.has(array)) {
@@ -3263,8 +3173,7 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3263
3173
  // Mark as having methods attached
3264
3174
  arraysWithMethodsAttached.add(array);
3265
3175
 
3266
- // Attach state properties ($loading, $error, $ready) as getters
3267
- // These need to be accessible on the array for Alpine expressions like $x.assets.$ready
3176
+ // State getters ($loading/$error/$ready) so $x.assets.$ready etc. resolve on the array
3268
3177
  Object.defineProperty(array, '$loading', {
3269
3178
  enumerable: false,
3270
3179
  configurable: true,
@@ -3323,8 +3232,7 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3323
3232
  });
3324
3233
  });
3325
3234
 
3326
- // Attach methods to the filtered result so chaining works (e.g., .$search().$query())
3327
- // This ensures the returned array has $query, $route, and other methods available
3235
+ // Re-attach so chaining works (.$search().$query())
3328
3236
  attachArrayMethods(filtered, dataSourceName, reloadDataSource);
3329
3237
 
3330
3238
  return filtered;
@@ -3344,11 +3252,10 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3344
3252
  });
3345
3253
  }
3346
3254
  if (array && typeof array === 'object') {
3347
- // Get raw data to ensure we have the actual array (not Alpine proxy)
3255
+ // Prefer raw data the real array, not the Alpine proxy
3348
3256
  const getRawData = window.ManifestDataStore?.getRawData;
3349
3257
  let dataToUse = array;
3350
3258
 
3351
- // CRITICAL: Always try to get raw data first - this ensures we have the real array
3352
3259
  if (dataSourceName && getRawData) {
3353
3260
  const rawData = getRawData(dataSourceName);
3354
3261
  if (rawData && (Array.isArray(rawData) || (rawData.length !== undefined && rawData.length >= 0))) {
@@ -3423,23 +3330,11 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3423
3330
  }
3424
3331
  }
3425
3332
 
3426
- // Attach client-side $query (overridden by Appwrite if source is Appwrite)
3427
- // Always attach even if dataSourceName empty (enables method chaining: .$search().$query())
3428
- //
3429
- // IMPORTANT: use the SYNCHRONOUS manifest accessor, not ensureManifest().
3430
- // ensureManifest() is `async function` calling it without `await` returns
3431
- // a Promise, and `Promise.data` is undefined, so the Appwrite-detection
3432
- // block silently fell through and isAppwriteSource stayed `false` for
3433
- // EVERY source — including real Appwrite collections. That caused the
3434
- // client-side $query to be attached to Appwrite arrays. The client-side
3435
- // $query sorts/filters in-memory and returns the result (without mutating
3436
- // the source), while demo code calls $query as a fire-and-forget store
3437
- // mutation. Result: sort buttons silently no-op'd.
3438
- //
3439
- // window.__manifestLoaded and window.ManifestComponentsRegistry.manifest
3440
- // are populated synchronously by the loader after the manifest fetch
3441
- // resolves (see manifest.js loader: `window.__manifestLoaded = manifest`).
3442
- // By the time any data source is being attached, they're available.
3333
+ // Client-side $query, overridden by Appwrite's for Appwrite sources.
3334
+ // Must use the SYNCHRONOUS manifest accessor — ensureManifest() is async,
3335
+ // so `.data` on its unawaited Promise is undefined and detection would
3336
+ // silently fall through, wrongly attaching client-side $query to Appwrite
3337
+ // arrays (in-memory sort discarded sort buttons no-op).
3443
3338
  let isAppwriteSource = false;
3444
3339
  if (dataSourceName) {
3445
3340
  try {
@@ -3455,8 +3350,7 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3455
3350
  }
3456
3351
  }
3457
3352
 
3458
- // Only attach client-side $query for non-Appwrite sources
3459
- // Appwrite sources will get their $query from the Appwrite plugin (attached later)
3353
+ // Appwrite sources get their $query from the Appwrite plugin instead
3460
3354
  if (!isAppwriteSource && !array.hasOwnProperty('$query')) {
3461
3355
  Object.defineProperty(array, '$query', {
3462
3356
  enumerable: false,
@@ -3560,8 +3454,7 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3560
3454
  }
3561
3455
  }
3562
3456
 
3563
- // Attach methods to the filtered result so chaining works (e.g., .$query().$search())
3564
- // This ensures the returned array has $search, $route, and other methods available
3457
+ // Re-attach so chaining works (.$query().$search())
3565
3458
  attachArrayMethods(result, dataSourceName, reloadDataSource);
3566
3459
 
3567
3460
  return result;
@@ -3571,12 +3464,7 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3571
3464
 
3572
3465
  // Attach Appwrite methods ($create, $update, $delete, etc.)
3573
3466
  if (dataSourceName) {
3574
- // Check if this is an Appwrite source. Same sync-manifest fix as
3575
- // the block above — ensureManifest() is async and returned a Promise
3576
- // here too, so isAppwriteSource was always false and the Appwrite
3577
- // $query was never attached, leaving sort/query buttons to silently
3578
- // fall through to the client-side $query that returns a discarded
3579
- // sorted array.
3467
+ // Sync manifest accessor again see note above on the async pitfall.
3580
3468
  let isAppwriteSource = false;
3581
3469
  try {
3582
3470
  const manifest = window.__manifestLoaded
@@ -3593,7 +3481,7 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3593
3481
  const createAppwriteMethodsHandler = window.ManifestDataProxiesAppwrite?.createAppwriteMethodsHandler;
3594
3482
  if (createAppwriteMethodsHandler) {
3595
3483
  const methodsHandler = createAppwriteMethodsHandler(dataSourceName, reloadDataSource);
3596
- // For Appwrite sources, include $query. For local sources, exclude it (base plugin handles it)
3484
+ // $query only for Appwrite sources; local sources keep the base plugin's
3597
3485
  const appwriteMethods = isAppwriteSource
3598
3486
  ? ['$create', '$update', '$delete', '$duplicate', '$query', '$url', '$download', '$preview', '$openUrl', '$openPreview', '$openDownload', '$filesFor', '$unlinkFrom', '$removeFrom', '$remove']
3599
3487
  : ['$create', '$update', '$delete', '$duplicate', '$url', '$download', '$preview', '$openUrl', '$openPreview', '$openDownload', '$filesFor', '$unlinkFrom', '$removeFrom', '$remove'];
@@ -3704,11 +3592,8 @@ if (typeof window !== 'undefined') {
3704
3592
  }
3705
3593
 
3706
3594
  function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDataSource = null) {
3707
- // When dataSourceName is provided, use a Map with composite key (array + dataSourceName)
3708
- // When dataSourceName is null, use WeakMap (original behavior)
3595
+ // With dataSourceName: Map keyed by (array id + name). Without: WeakMap by array.
3709
3596
  if (dataSourceName) {
3710
- // Create a composite key using array identity and dataSourceName
3711
- // Use a WeakMap to store a unique ID for each array, then use that ID + dataSourceName in Map
3712
3597
  if (!window._arrayProxyIdMap) {
3713
3598
  window._arrayProxyIdMap = new WeakMap();
3714
3599
  window._arrayProxyIdCounter = 0;
@@ -3731,14 +3616,11 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
3731
3616
  }
3732
3617
  }
3733
3618
 
3734
- // Attach array methods directly to target BEFORE creating proxy (ensures Alpine compatibility)
3619
+ // Attach array methods to the target before proxying (safety net if Alpine reads them directly)
3735
3620
  if (Array.isArray(arrayTarget) && !arraysWithMethodsAttached.has(arrayTarget)) {
3736
- // Attach all standard array methods directly to the array
3737
- // This is a safety net in case Alpine accesses methods directly on the array
3738
3621
  const attachedMethods = [];
3739
3622
  ARRAY_METHODS.forEach(methodName => {
3740
3623
  if (!(methodName in arrayTarget) || typeof arrayTarget[methodName] !== 'function') {
3741
- // Only attach if not already present (to avoid overwriting)
3742
3624
  try {
3743
3625
  Object.defineProperty(arrayTarget, methodName, {
3744
3626
  enumerable: false,
@@ -3758,15 +3640,14 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
3758
3640
  } else {
3759
3641
  }
3760
3642
 
3761
- // Pre-create $files function if this is a table data source
3762
- // Always create it, even if manifest isn't loaded yet - the function will handle it internally
3643
+ // Pre-create $files (function validates table-vs-bucket at call time)
3763
3644
  if (dataSourceName) {
3764
3645
  const createFilesMethod = window.ManifestDataProxiesMagic?.createFilesMethod;
3765
3646
  if (createFilesMethod && !arrayTarget._$filesFunction) {
3766
3647
  arrayTarget._$filesFunction = createFilesMethod(dataSourceName);
3767
3648
  Object.defineProperty(arrayTarget._$filesFunction, 'name', { value: '$files', configurable: true });
3768
3649
  Object.setPrototypeOf(arrayTarget._$filesFunction, Function.prototype);
3769
- // Also define it directly on the array target so it's accessible without going through proxy
3650
+ // Also define on the target so it's reachable without the proxy
3770
3651
  Object.defineProperty(arrayTarget, '$files', {
3771
3652
  enumerable: true,
3772
3653
  configurable: true,
@@ -3776,9 +3657,7 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
3776
3657
  }
3777
3658
  }
3778
3659
 
3779
- // Attach $route directly to the array target (like attachArrayMethods does)
3780
- // This ensures $route() works even if Alpine proxies the proxy
3781
- // Always attach it, even if createRouteProxy isn't available yet (it will be checked at call time)
3660
+ // Attach $route to the target too, so it survives Alpine proxying the proxy
3782
3661
  if (!arrayTarget.hasOwnProperty('$route')) {
3783
3662
  Object.defineProperty(arrayTarget, '$route', {
3784
3663
  enumerable: false,
@@ -3792,11 +3671,10 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
3792
3671
  });
3793
3672
  }
3794
3673
  if (arrayTarget && typeof arrayTarget === 'object') {
3795
- // Get raw data to ensure we have the actual array (not Alpine proxy)
3674
+ // Prefer raw data the real array, not the Alpine proxy
3796
3675
  const getRawData = window.ManifestDataStore?.getRawData;
3797
3676
  let dataToUse = arrayTarget;
3798
3677
 
3799
- // CRITICAL: Always try to get raw data first - this ensures we have the real array
3800
3678
  if (dataSourceName && getRawData) {
3801
3679
  const rawData = getRawData(dataSourceName);
3802
3680
  if (rawData && (Array.isArray(rawData) || (rawData.length !== undefined && rawData.length >= 0))) {
@@ -3804,7 +3682,7 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
3804
3682
  }
3805
3683
  }
3806
3684
 
3807
- // If we still don't have raw data, try to convert the proxy to a real array
3685
+ // Otherwise coerce the array-like proxy into a real array
3808
3686
  if (!Array.isArray(dataToUse) && dataToUse && typeof dataToUse === 'object' && 'length' in dataToUse) {
3809
3687
  try {
3810
3688
  dataToUse = Array.from(dataToUse);
@@ -3830,17 +3708,16 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
3830
3708
  });
3831
3709
  }
3832
3710
 
3833
- // Create the base array proxy
3711
+ // Base array proxy
3834
3712
  const baseProxy = Object.setPrototypeOf(
3835
3713
  new Proxy(arrayTarget, {
3836
3714
  get(target, key, receiver) {
3837
3715
 
3838
- // Handle special keys - but allow Symbol.iterator for array iteration
3839
3716
  if (key === 'then' || key === 'catch' || key === 'finally') {
3840
3717
  return undefined;
3841
3718
  }
3842
3719
 
3843
- // Allow Symbol.iterator for proper array iteration (needed for Alpine's x-for)
3720
+ // Symbol.iterator needed for Alpine's x-for
3844
3721
  if (key === Symbol.iterator) {
3845
3722
  const manifest = window.ManifestDataConfig?.getManifest?.();
3846
3723
  const ds = manifest?.data?.[dataSourceName];
@@ -3848,7 +3725,7 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
3848
3725
  if (isStorageBucket) {
3849
3726
  }
3850
3727
  const iterator = target[Symbol.iterator].bind(target);
3851
- // Ensure iterator function has proper prototype for Alpine's instanceof checks
3728
+ // Restore Function.prototype for Alpine's instanceof checks
3852
3729
  if (iterator && typeof iterator === 'function') {
3853
3730
  Object.setPrototypeOf(iterator, Function.prototype);
3854
3731
  }
@@ -3872,14 +3749,11 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
3872
3749
  return null;
3873
3750
  }
3874
3751
 
3875
- // Handle $route function for route-specific lookups on arrays
3876
- // Also check if it's attached directly to the target (for nested arrays)
3752
+ // $route prefer one attached to the target (nested arrays)
3877
3753
  if (key === '$route') {
3878
- // First check if it's attached directly to the target
3879
3754
  if (target.$route && typeof target.$route === 'function') {
3880
3755
  return target.$route;
3881
3756
  }
3882
- // Otherwise, return the function from the proxy handler
3883
3757
  const createRouteProxy = window.ManifestDataProxies?.createRouteProxy;
3884
3758
  if (!createRouteProxy) {
3885
3759
  // Return a function that returns a safe proxy (not a proxy directly)
@@ -4086,9 +3960,7 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
4086
3960
  return undefined;
4087
3961
  }
4088
3962
 
4089
- // Handle Appwrite methods for arrays (when data source is an Appwrite table or bucket)
4090
- // These methods are called on the array itself (e.g., $x.assets.$create(file))
4091
- // Only available if Appwrite plugin is loaded
3963
+ // Appwrite methods called on the array itself (e.g. $x.assets.$create(file))
4092
3964
  if (dataSourceName && (key === '$create' || key === '$update' || key === '$delete' || key === '$duplicate' || key === '$query' ||
4093
3965
  key === '$url' || key === '$download' || key === '$preview' || key === '$filesFor' || key === '$unlinkFrom' || key === '$removeFrom' || key === '$remove')) {
4094
3966
  // Check if Appwrite methods handler is available
@@ -4310,9 +4182,8 @@ function createArrayProxyWithRoute(arrayTarget, dataSourceName = null, reloadDat
4310
4182
  });
4311
4183
  }
4312
4184
 
4313
- // Cache the base proxy IMMEDIATELY after creation, before returning
4314
- // This ensures the cache is available if the proxy's get handler is called recursively
4315
- // SKIP CACHE for 'projects' to ensure Alpine gets fresh proxy reference for reactivity
4185
+ // Cache before returning (get handler may recurse). Skip 'projects' so
4186
+ // Alpine always gets a fresh proxy reference for reactivity.
4316
4187
  if (dataSourceName && dataSourceName !== 'projects') {
4317
4188
  // Use Map cache with composite key
4318
4189
  if (!window._arrayProxyIdMap) {
@@ -6734,37 +6605,18 @@ window.ManifestDataProxiesFiles.buildStoragePermissions = buildStoragePermission
6734
6605
  window.ManifestDataProxiesFiles.reactiveFileManagers = reactiveFileManagers; // Legacy - will be removed
6735
6606
 
6736
6607
  /* Manifest Data Sources - Route & Proxy Coordinator */
6737
- // This file coordinates the proxy creation modules and re-exports their functions
6738
- // The actual implementations are in:
6739
- // - proxies/creation/manifest.data.proxies.helpers.js (helper functions)
6740
- // - proxies/creation/manifest.data.proxies.array.js (array proxy creation)
6741
- // - proxies/creation/manifest.data.proxies.object.js (object proxy creation)
6742
- // - proxies/creation/manifest.data.proxies.route.js (route proxy creation)
6743
-
6744
- // Re-export functions from proxy creation modules for backward compatibility
6745
- // These modules export to window.ManifestDataProxies, so we just ensure the namespace exists
6608
+ // Proxy creation lives in proxies/creation/*; those modules self-export to
6609
+ // window.ManifestDataProxies. This file only ensures the namespace exists.
6746
6610
  if (typeof window !== 'undefined') {
6747
6611
  if (!window.ManifestDataProxies) {
6748
6612
  window.ManifestDataProxies = {};
6749
6613
  }
6750
-
6751
- // Functions are already exported by the individual modules:
6752
- // - createArrayProxyWithRoute (from array.js)
6753
- // - createRouteProxy (from route.js)
6754
- // - createNestedObjectProxy (from object.js)
6755
- // - clearRouteProxyCacheForDataSource (from route.js)
6756
- // - clearArrayProxyCacheForDataSource (from array.js)
6757
- // - attachArrayMethods (from array.js)
6758
-
6759
- // This file serves as a coordinator and ensures all modules are loaded
6760
- // The build system includes these files in the correct order before this file
6761
6614
  }
6762
6615
 
6763
6616
 
6764
6617
  /* Manifest Data Sources - Appwrite Methods Handler */
6765
- // Create Appwrite methods handler for tables and buckets
6618
+ // CRUD/storage methods handler for Appwrite tables and buckets
6766
6619
  function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
6767
- // Helper to set error state automatically
6768
6620
  const setErrorState = (error) => {
6769
6621
  const store = Alpine.store('data');
6770
6622
  if (store) {
@@ -6780,11 +6632,9 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
6780
6632
  };
6781
6633
  Alpine.store('data', updatedStore);
6782
6634
  }
6783
- // For test purposes, also log to console
6784
6635
  console.error(`[Manifest Data] ${dataSourceName} operation failed:`, error);
6785
6636
  };
6786
6637
 
6787
- // Helper to clear error state
6788
6638
  const clearErrorState = () => {
6789
6639
  const store = Alpine.store('data');
6790
6640
  if (store) {
@@ -6804,13 +6654,11 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
6804
6654
  }
6805
6655
  };
6806
6656
 
6807
- // Core method handler logic (extracted for recursive calls)
6657
+ // Core handler (named so $duplicate etc. can recurse)
6808
6658
  const handleMethod = async function (method, ...args) {
6809
- // Clear error state before operation
6810
6659
  clearErrorState();
6811
6660
 
6812
6661
  try {
6813
- // Get manifest to check if this is an Appwrite data source
6814
6662
  const manifest = await window.ManifestDataConfig?.ensureManifest?.();
6815
6663
  if (!manifest?.data) {
6816
6664
  throw new Error('[Manifest Data] Manifest not available');
@@ -6861,7 +6709,7 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
6861
6709
  }
6862
6710
  }
6863
6711
 
6864
- // Use unified mutation system with optimistic updates
6712
+ // Optimistic mutation path
6865
6713
  const executeMutation = window.ManifestDataMutations?.executeMutation;
6866
6714
  if (executeMutation) {
6867
6715
  return await executeMutation({
@@ -7452,11 +7300,8 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
7452
7300
  addEntryToStore(dataSourceName, result);
7453
7301
  }
7454
7302
 
7455
- // If entryId is provided, link the file to a table entry
7456
- // Supports multiple API styles for flexibility:
7457
- // 1. $x.assets.$create(file, null, null, { entryId: '...', table: 'projects' })
7458
- // 2. $x.assets.$create(file, null, null, { entryId: '...', table: 'projects', fileIdsColumn: 'attachments' })
7459
- // 3. $x.assets.$create(file, null, null, null, 'entryId') // Legacy: 4th arg as entryId
7303
+ // Link the uploaded file to a table entry, resolved from the
7304
+ // options object (4th arg), the legacy 5th-arg id, or belongsTo.
7460
7305
  let entryId = null;
7461
7306
  let tableName = null;
7462
7307
  let fileIdsColumn = 'fileIds'; // Default column name
@@ -7507,11 +7352,8 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
7507
7352
  }
7508
7353
  }
7509
7354
 
7510
- // For storage buckets, we already have the real file from API response
7511
- // No need for background reload - the optimistic update was already replaced with real file
7512
- // Background reload would only be needed if we need to apply scope filtering,
7513
- // but since we have the real file object, we can trust it's correct
7514
- // If scope filtering is needed, it will be handled by realtime events
7355
+ // We already have the real file from the API response, so no
7356
+ // background reload; scope filtering (if any) is handled by realtime.
7515
7357
  if (!addEntryToStore) {
7516
7358
  // Fallback to old behavior
7517
7359
  if (window.ManifestDataStore?.dataSourceCache) {
@@ -7584,9 +7426,8 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
7584
7426
  })
7585
7427
  );
7586
7428
 
7587
- // SINGLE SOURCE OF TRUTH: No reload needed - optimistic delete provides immediate feedback
7588
- // and realtime events will sync everything automatically. Reloading causes race conditions
7589
- // where stale data overwrites optimistic deletes, causing files to reappear.
7429
+ // No reload: optimistic delete + realtime sync. Reloading
7430
+ // races and can resurrect deleted files from stale data.
7590
7431
  return results;
7591
7432
  } else {
7592
7433
  // Fallback to old behavior
@@ -7614,9 +7455,8 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
7614
7455
  }
7615
7456
  });
7616
7457
 
7617
- // SINGLE SOURCE OF TRUTH: No reload needed - optimistic delete provides immediate feedback
7618
- // and realtime events will sync everything automatically. Reloading causes race conditions
7619
- // where stale data overwrites optimistic deletes, causing files to reappear.
7458
+ // No reload: optimistic delete + realtime sync. Reloading
7459
+ // races and can resurrect deleted files from stale data.
7620
7460
  return result;
7621
7461
  } else {
7622
7462
  // Fallback to old behavior
@@ -7651,40 +7491,15 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
7651
7491
  throw new Error(`[Manifest Data] File "${actualFileId}" not found or not accessible: ${error.message}`);
7652
7492
  }
7653
7493
 
7654
- // Get view URL using Appwrite SDK (a URL STRING, not the
7655
- // file content). Using 'view' rather than 'download' since
7656
- // we're re-uploading rather than saving to disk.
7494
+ // 'view' URL (a string, not the bytes) since we re-upload rather than save to disk
7657
7495
  const viewUrl = await window.ManifestDataAppwrite.getFileURL(bucketId, actualFileId);
7658
7496
 
7659
- // Authenticate the fetch for permissioned buckets.
7660
- //
7661
- // Storage's /view, /download and /preview endpoints check
7662
- // the USER SESSION not API/dev keys (those work only on
7663
- // JSON endpoints like /storage/buckets/.../files/.../).
7664
- // In localhost dev the browser blocks the cross-domain
7665
- // session cookie (SameSite=Lax on plain HTTP), so the
7666
- // request lands at Appwrite as anonymous and a permissioned
7667
- // file returns 404 storage_file_not_found.
7668
- //
7669
- // The Appwrite Web SDK works around this by writing the
7670
- // session token to localStorage under `cookieFallback`
7671
- // and replaying it as the `X-Fallback-Cookies` header on
7672
- // every SDK request. That's why SDK calls (getFile metadata
7673
- // above, listRows, $create, etc.) succeed cross-domain
7674
- // while raw fetch() doesn't — raw fetch doesn't know to
7675
- // read that localStorage key.
7676
- //
7677
- // We do exactly what the SDK does: read cookieFallback
7678
- // and attach it as X-Fallback-Cookies. This is more
7679
- // reliable than JWT:
7680
- // - no createJWT round-trip (and dev-key-configured
7681
- // clients 501 on createJWT)
7682
- // - no 15-min expiry or rate limit (10/hour/account)
7683
- // - matches whatever auth the SDK is already using
7684
- //
7685
- // Falls through to credentials-only when the user isn't
7686
- // signed in (no cookieFallback in storage) — that path
7687
- // still works in production with a SameSite=None cookie.
7497
+ // Storage /view checks the user SESSION, not API/dev keys. In
7498
+ // localhost dev the cross-domain session cookie is blocked
7499
+ // (SameSite=Lax on HTTP) so a raw fetch lands anonymous → 404.
7500
+ // Replicate the SDK's workaround: replay the `cookieFallback`
7501
+ // localStorage token as X-Fallback-Cookies. Falls through to
7502
+ // credentials-only when signed out (works in prod via SameSite=None).
7688
7503
  const fetchHeaders = {};
7689
7504
  if (appwriteConfig.projectId) {
7690
7505
  fetchHeaders['X-Appwrite-Project'] = appwriteConfig.projectId;
@@ -8068,14 +7883,8 @@ window.ManifestDataProxiesAppwrite.createAppwriteMethodsHandler = createAppwrite
8068
7883
 
8069
7884
 
8070
7885
  /* Manifest Data Sources - Magic Method State Properties */
8071
- // Handles $loading, $error, $ready state properties
7886
+ // $loading, $error, $ready state properties
8072
7887
 
8073
- /**
8074
- * Get state property value for a data source
8075
- * @param {string} prop - Property name ($loading, $error, $ready)
8076
- * @param {string} dataSourceName - Name of the data source
8077
- * @returns {boolean|string|null} State value
8078
- */
8079
7888
  function getStateProperty(prop, dataSourceName) {
8080
7889
  if (typeof Alpine === 'undefined' || !Alpine.store) {
8081
7890
  return prop === '$loading' ? false : (prop === '$error' ? null : false);
@@ -8100,10 +7909,7 @@ function getStateProperty(prop, dataSourceName) {
8100
7909
  return undefined;
8101
7910
  }
8102
7911
 
8103
- /**
8104
- * Create a state property handler for loading proxies
8105
- * Returns a function that can be used in proxy get handlers
8106
- */
7912
+ // State property handler for use in proxy get() traps
8107
7913
  function createStatePropertyHandler(dataSourceName) {
8108
7914
  return function (key) {
8109
7915
  if (key === '$loading' || key === '$error' || key === '$ready') {
@@ -8228,14 +8034,8 @@ window.ManifestDataProxiesMagic.createFilesMethod = createFilesMethod;
8228
8034
 
8229
8035
 
8230
8036
  /* Manifest Data Sources - Magic Method $upload Handler */
8231
- // Handles $upload method for uploading files and linking to table entries
8037
+ // $upload: upload files and link them to a table entry
8232
8038
 
8233
- /**
8234
- * Create $upload method for a data source
8235
- * @param {string} dataSourceName - Name of the data source (table)
8236
- * @param {Function} reloadDataSource - Function to reload data source
8237
- * @returns {Function} $upload method function
8238
- */
8239
8039
  function createUploadMethod(dataSourceName, reloadDataSource) {
8240
8040
  return async function (entryId, fileOrEvent, bucketName) {
8241
8041
  const manifest = await window.ManifestDataConfig.ensureManifest();
@@ -8350,10 +8150,7 @@ function createUploadMethod(dataSourceName, reloadDataSource) {
8350
8150
 
8351
8151
  const results = await Promise.all(uploadPromises);
8352
8152
 
8353
- // NOTE: No need to manually update file managers - computed files arrays
8354
- // automatically update when bucket array changes (single source of truth)
8355
-
8356
- // Return single file or array of files
8153
+ // Computed files arrays update automatically when the bucket array changes.
8357
8154
  return files.length === 1 ? results[0] : results;
8358
8155
  } catch (error) {
8359
8156
  // Clear all uploading states on error
@@ -8376,14 +8173,8 @@ window.ManifestDataProxiesMagic.createUploadMethod = createUploadMethod;
8376
8173
 
8377
8174
 
8378
8175
  /* Manifest Data Sources - Magic Method Pagination Handlers */
8379
- // Handles pagination methods ($first, $next, $prev, $page)
8176
+ // $first, $next, $prev, $page
8380
8177
 
8381
- /**
8382
- * Create pagination method handler
8383
- * @param {string} methodName - Method name ($first, $next, $prev, $page)
8384
- * @param {string} dataSourceName - Name of the data source
8385
- * @returns {Function} Pagination method function
8386
- */
8387
8178
  function createPaginationMethod(methodName, dataSourceName) {
8388
8179
  return async function (...args) {
8389
8180
  const manifest = await window.ManifestDataConfig.ensureManifest();
@@ -8396,7 +8187,7 @@ function createPaginationMethod(methodName, dataSourceName) {
8396
8187
  throw new Error(`[Manifest Data] Pagination is only supported for Appwrite data sources`);
8397
8188
  }
8398
8189
 
8399
- // Get base queries (from manifest or scope)
8190
+ // Base queries from manifest or scope
8400
8191
  const scope = window.ManifestDataConfig.getScope(dataSource);
8401
8192
  const queriesConfig = window.ManifestDataConfig.getQueries(dataSource);
8402
8193
  const baseQueries = queriesConfig
@@ -8437,10 +8228,9 @@ window.ManifestDataProxiesMagic.createPaginationMethod = createPaginationMethod;
8437
8228
 
8438
8229
 
8439
8230
  /* Manifest Data Sources - Magic Method Core Registration */
8440
- // Main proxy creation and registration - delegates to helper modules
8231
+ // $x proxy creation/registration; delegates to helper modules.
8441
8232
 
8442
- // Expose $x globally IMMEDIATELY (at module load time) so it's available before Alpine initializes
8443
- // This ensures window.$x works in x-data methods and other contexts
8233
+ // Expose window.$x at module load (before Alpine) so it works in x-data methods
8444
8234
  if (typeof window !== 'undefined') {
8445
8235
  // Create a cached fallback proxy (reuse same instance for chaining)
8446
8236
  let cachedFallbackProxy = null;
@@ -8449,9 +8239,7 @@ if (typeof window !== 'undefined') {
8449
8239
  try {
8450
8240
  Object.defineProperty(window, '$x', {
8451
8241
  get: function () {
8452
- // Try multiple methods to get the proxy:
8453
-
8454
- // 1. Try stored factory function (most reliable)
8242
+ // 1. Stored factory (most reliable)
8455
8243
  if (window._$xProxyFactory && typeof window._$xProxyFactory === 'function') {
8456
8244
  try {
8457
8245
  const proxy = window._$xProxyFactory();
@@ -8461,7 +8249,7 @@ if (typeof window !== 'undefined') {
8461
8249
  }
8462
8250
  }
8463
8251
 
8464
- // 2. Try Alpine magic method
8252
+ // 2. Alpine magic method
8465
8253
  try {
8466
8254
  const magicFn = window.Alpine?.magic?.('x');
8467
8255
  if (magicFn && typeof magicFn === 'function') {
@@ -8469,28 +8257,23 @@ if (typeof window !== 'undefined') {
8469
8257
  if (proxy) return proxy;
8470
8258
  }
8471
8259
  } catch (e) {
8472
- // Magic method not ready or failed - continue to fallback
8260
+ // Not ready continue to fallback
8473
8261
  }
8474
8262
 
8475
- // 3. Fallback: return a safe loading proxy that allows chaining
8476
- // This allows code to run without errors while Alpine initializes
8477
- // Use the same loading proxy pattern used elsewhere in the codebase
8263
+ // 3. Loading proxy (lets code run while Alpine initializes)
8478
8264
  const createLoadingProxy = window.ManifestDataProxiesCore?.createLoadingProxy;
8479
8265
  if (createLoadingProxy) {
8480
8266
  const loadingProxy = createLoadingProxy();
8481
8267
  if (loadingProxy) return loadingProxy;
8482
8268
  }
8483
8269
 
8484
- // Ultimate fallback: return a cached proxy that returns itself for chaining
8485
- // Cache it so chaining works (window.$x.projects.$upload returns the same proxy)
8270
+ // 4. Cached self-chaining proxy (same instance keeps chaining safe)
8486
8271
  if (!cachedFallbackProxy) {
8487
8272
  cachedFallbackProxy = new Proxy({}, {
8488
8273
  get(target, prop) {
8489
- // Return the same proxy for chaining (allows window.$x.projects.$upload without errors)
8490
8274
  return cachedFallbackProxy;
8491
8275
  },
8492
8276
  has(target, prop) {
8493
- // Make all properties appear to exist to prevent Alpine errors
8494
8277
  return true;
8495
8278
  }
8496
8279
  });
@@ -8506,12 +8289,7 @@ if (typeof window !== 'undefined') {
8506
8289
  }
8507
8290
  }
8508
8291
 
8509
- /**
8510
- * Create a loading proxy with methods for Appwrite data sources
8511
- * @param {string} dataSourceName - Name of the data source
8512
- * @param {Function} reloadDataSource - Function to reload data source
8513
- * @returns {Proxy} Loading proxy with methods
8514
- */
8292
+ // Loading proxy for Appwrite sources (CRUD + state/files/upload/pagination methods)
8515
8293
  function createAppwriteLoadingProxy(dataSourceName, reloadDataSource) {
8516
8294
  const createLoadingProxy = window.ManifestDataProxiesCore?.createLoadingProxy;
8517
8295
  const createAppwriteMethodsHandler = window.ManifestDataProxiesAppwrite?.createAppwriteMethodsHandler;
@@ -8534,7 +8312,6 @@ function createAppwriteLoadingProxy(dataSourceName, reloadDataSource) {
8534
8312
 
8535
8313
  return new Proxy(createLoadingProxy(), {
8536
8314
  get(target, key) {
8537
- // Handle state properties
8538
8315
  if (stateHandler) {
8539
8316
  const stateValue = stateHandler(key);
8540
8317
  if (stateValue !== undefined) {
@@ -8542,22 +8319,19 @@ function createAppwriteLoadingProxy(dataSourceName, reloadDataSource) {
8542
8319
  }
8543
8320
  }
8544
8321
 
8545
- // Handle $files method for tables (reactive file arrays) - available even when loading
8322
+ // $files / $upload available even while loading
8546
8323
  if (key === '$files' && filesMethod) {
8547
8324
  return filesMethod;
8548
8325
  }
8549
-
8550
- // Handle $upload method for tables - available even when loading
8551
8326
  if (key === '$upload' && uploadMethod) {
8552
8327
  return uploadMethod;
8553
8328
  }
8554
8329
 
8555
- // Handle pagination methods
8556
8330
  if ((key === '$first' || key === '$next' || key === '$prev' || key === '$page') && createPaginationMethod) {
8557
8331
  return createPaginationMethod(key, dataSourceName);
8558
8332
  }
8559
8333
 
8560
- // Handle Appwrite CRUD methods
8334
+ // Appwrite CRUD
8561
8335
  if (key === '$create' || key === '$update' || key === '$delete' || key === '$query' ||
8562
8336
  key === '$url' || key === '$download' || key === '$preview' || key === '$filesFor' ||
8563
8337
  key === '$unlinkFrom' || key === '$removeFrom' || key === '$remove') {
@@ -8566,17 +8340,12 @@ function createAppwriteLoadingProxy(dataSourceName, reloadDataSource) {
8566
8340
  }
8567
8341
  }
8568
8342
 
8569
- // Fall through to loading proxy
8570
8343
  return target[key];
8571
8344
  }
8572
8345
  });
8573
8346
  }
8574
8347
 
8575
- /**
8576
- * Create a loading proxy for non-Appwrite data sources
8577
- * @param {string} dataSourceName - Name of the data source
8578
- * @returns {Proxy} Loading proxy with basic methods
8579
- */
8348
+ // Loading proxy for non-Appwrite sources (state + $files only)
8580
8349
  function createBasicLoadingProxy(dataSourceName) {
8581
8350
  const createLoadingProxy = window.ManifestDataProxiesCore?.createLoadingProxy;
8582
8351
  const createStatePropertyHandler = window.ManifestDataProxiesMagic?.createStatePropertyHandler;
@@ -8591,7 +8360,6 @@ function createBasicLoadingProxy(dataSourceName) {
8591
8360
 
8592
8361
  return new Proxy(createLoadingProxy(), {
8593
8362
  get(target, key) {
8594
- // Handle state properties
8595
8363
  if (stateHandler) {
8596
8364
  const stateValue = stateHandler(key);
8597
8365
  if (stateValue !== undefined) {
@@ -8599,7 +8367,6 @@ function createBasicLoadingProxy(dataSourceName) {
8599
8367
  }
8600
8368
  }
8601
8369
 
8602
- // Handle $files method for tables (reactive file arrays)
8603
8370
  if (key === '$files' && filesMethod) {
8604
8371
  return filesMethod;
8605
8372
  }
@@ -8609,23 +8376,16 @@ function createBasicLoadingProxy(dataSourceName) {
8609
8376
  });
8610
8377
  }
8611
8378
 
8612
- /**
8613
- * Register the $x magic method with Alpine
8614
- * @param {Function} loadDataSource - Function to load data sources
8615
- */
8379
+ // Register the $x magic method with Alpine
8616
8380
  function registerXMagicMethod(loadDataSource) {
8617
- // Ensure Alpine is loaded before registering magic method
8618
8381
  if (typeof Alpine === 'undefined') {
8619
8382
  console.error('[Manifest Data] Alpine.js must be loaded before manifest.data.js');
8620
8383
  return;
8621
8384
  }
8622
8385
 
8623
- // Store the proxy-creating function so we can access it directly
8624
8386
  let $xProxyFactory = null;
8625
8387
 
8626
- // CRITICAL: Return the same proxy instance every time so the re-entrancy guard (magicGetDepth)
8627
- // works. If we created a new proxy per magic('x') call, each would have its own depth and we'd
8628
- // never see depth > 1 when Alpine re-enters during store reads.
8388
+ // Same proxy instance every call so the magicGetDepth re-entrancy guard actually counts re-entries.
8629
8389
  let cachedMagicProxy = null;
8630
8390
 
8631
8391
  const magicFunction = (el) => {
@@ -8641,13 +8401,10 @@ function registerXMagicMethod(loadDataSource) {
8641
8401
  // Store loadDataSource in closure for Appwrite methods
8642
8402
  const reloadDataSource = loadDataSource;
8643
8403
 
8644
- // Track active property accesses to prevent circular references
8645
- // Use a symbol to store the active props set on each proxy call
8646
8404
  const ACTIVE_PROPS = Symbol('activeProps');
8647
8405
 
8648
- // Re-entrancy guard: deep recursion when reading the store can cause stack overflow.
8649
- // Use a high threshold so we only break true overflow; Alpine may re-enter once when
8650
- // we read Alpine.store('data'), and we must return real data for that to render.
8406
+ // Re-entrancy guard against store-read recursion stack overflow. High
8407
+ // threshold so we only break true overflow (Alpine may re-enter once).
8651
8408
  let magicGetDepth = 0;
8652
8409
  const MAGIC_GET_MAX_DEPTH = 12;
8653
8410
 
@@ -8662,17 +8419,14 @@ function registerXMagicMethod(loadDataSource) {
8662
8419
  return fallback !== undefined ? fallback : '';
8663
8420
  }
8664
8421
  try {
8665
- // Handle special keys
8666
8422
  if (prop === Symbol.iterator || prop === 'then' || prop === 'catch' || prop === 'finally') {
8667
8423
  return undefined;
8668
8424
  }
8669
8425
 
8670
- // CRITICAL: Resolve from raw data and cache BEFORE reading Alpine.store('data').
8671
- // Reading the store registers a reactive dependency and can trigger Alpine to re-run
8672
- // the current effect (e.g. :aria-label="$x.content.theme.light"). If we haven't cached
8673
- // yet, the re-run calls get(proxy, 'content') again and we read the store again → stack overflow.
8674
- // By using getRawData (non-reactive) first and caching the nested proxy, the re-run hits
8675
- // the cache and returns without touching the store.
8426
+ // Resolve+cache from raw data BEFORE reading Alpine.store('data'):
8427
+ // the store read registers a reactive dep that can re-run this
8428
+ // effect and re-enter get() → stack overflow. Caching first makes
8429
+ // the re-run hit the cache without touching the store.
8676
8430
  const getRawDataEarly = window.ManifestDataStore?.getRawData;
8677
8431
  const rawValueEarly = getRawDataEarly ? getRawDataEarly(prop) : null;
8678
8432
  if (!window.ManifestDataProxiesCore.nestedDataSourceProxyCache) {
@@ -8691,7 +8445,7 @@ function registerXMagicMethod(loadDataSource) {
8691
8445
  }
8692
8446
  }
8693
8447
  if (nestedCache.has(prop) && !hasData) nestedCache.delete(prop);
8694
- // Build and cache from raw before any store read, for object data sources (e.g. content, manifest)
8448
+ // Object sources (content, manifest): build+cache from raw before any store read
8695
8449
  if (hasData && rawValueEarly && typeof rawValueEarly === 'object' && !Array.isArray(rawValueEarly)) {
8696
8450
  const createNestedObjectProxy = window.ManifestDataProxies?.createNestedObjectProxy;
8697
8451
  if (createNestedObjectProxy) {
@@ -8709,10 +8463,9 @@ function registerXMagicMethod(loadDataSource) {
8709
8463
  }
8710
8464
  }
8711
8465
 
8712
- // When we have no raw data yet: start load, subscribe so effect re-runs when data loads, then return loading proxy.
8713
- // We must read a store primitive (_dataVersion) so Alpine tracks the dependency; otherwise when
8714
- // updateStore runs the effect never re-runs and UI stays on loading proxy. We only read the version,
8715
- // never return store data, so no re-entry/stack overflow.
8466
+ // No raw data yet: start load, subscribe (read _dataVersion so
8467
+ // Alpine tracks the dep and re-runs on updateStore), return loading
8468
+ // proxy. Reading only the version avoids re-entry/overflow.
8716
8469
  if (!hasData) {
8717
8470
  if (!pendingLoads.has(prop)) {
8718
8471
  const locale = typeof document !== 'undefined' && document.documentElement
@@ -8736,10 +8489,9 @@ function registerXMagicMethod(loadDataSource) {
8736
8489
  const currentStoreForCache = Alpine.store('data');
8737
8490
  void (currentStoreForCache && currentStoreForCache._dataVersion);
8738
8491
 
8739
- // Don't use activeProps circular check here: reading Alpine.store() can trigger Alpine to
8740
- // re-run effects that evaluate $x.json again, so we get re-entrant get(proxy, 'json') while
8741
- // the first call is still running. Treating that as "circular" returned a loading proxy and
8742
- // broke rendering. Stack overflow is prevented by MAGIC_GET_MAX_DEPTH instead.
8492
+ // No activeProps circular check here: a legit store-read re-entry
8493
+ // would be misread as circular and return a loading proxy, breaking
8494
+ // rendering. MAGIC_GET_MAX_DEPTH guards overflow instead.
8743
8495
  const propKey = String(prop);
8744
8496
  const activeProps = target[ACTIVE_PROPS] || (target[ACTIVE_PROPS] = new Set());
8745
8497
 
@@ -8752,16 +8504,12 @@ function registerXMagicMethod(loadDataSource) {
8752
8504
  return undefined;
8753
8505
  }
8754
8506
 
8755
- // Get raw data first (unproxied) to check if it's an array
8756
8507
  const getRawData = window.ManifestDataStore?.getRawData;
8757
8508
  const rawValue = getRawData ? getRawData(prop) : null;
8758
8509
 
8759
- // Get value from Alpine store (may be proxied)
8760
- // CRITICAL: We need to access currentStore[prop] for Alpine reactivity to work
8761
- // But we'll use rawValue when creating nested proxies to avoid circular references
8510
+ // Read currentStore[prop] for reactivity, but build nested proxies from rawValue
8762
8511
  let value = currentStore[prop];
8763
8512
 
8764
- // If value exists in store, return it immediately with proper proxy
8765
8513
  if (value !== undefined && value !== null || rawValue !== undefined && rawValue !== null) {
8766
8514
  // Clear any cached loading proxy for this data source
8767
8515
  const globalAccessCache = window.ManifestDataProxies?.globalAccessCache;
@@ -8834,33 +8582,23 @@ function registerXMagicMethod(loadDataSource) {
8834
8582
  }
8835
8583
 
8836
8584
  if (arrayToProxy && (Array.isArray(arrayToProxy) || rawIsArrayLike || valueIsArrayLike)) {
8837
- // CRITICAL CHANGE: Use attachArrayMethods instead of createArrayProxyWithRoute
8838
- // Alpine wraps our proxy and can't see methods defined on the proxy object
8839
- // By attaching methods directly to the array, Alpine can see them even when it wraps
8585
+ // attachArrayMethods (not createArrayProxyWithRoute): methods live on
8586
+ // the array itself so Alpine sees them even after wrapping our proxy.
8840
8587
  const attachArrayMethods = window.ManifestDataProxies?.attachArrayMethods;
8841
8588
  const arrayForMethods = valueIsArray ? value : (rawIsArray ? rawValue : arrayToProxy);
8842
8589
 
8843
8590
  if (attachArrayMethods) {
8844
8591
  const arrayWithMethods = attachArrayMethods(arrayForMethods, prop, loadDataSource);
8845
- // CRITICAL: Wrap in a proxy with has() trap so Alpine can see $search, $query, etc.
8846
- // Alpine uses has() to check if properties exist before accessing them
8592
+ // has() trap so Alpine sees $search/$query/etc. before accessing
8847
8593
  return new Proxy(arrayWithMethods, {
8848
8594
  get(target, key) {
8849
- // CRITICAL: Explicitly handle base plugin methods first
8850
8595
  if (key === '$search' || key === '$query' || key === '$route') {
8851
8596
  if (key in target && typeof target[key] === 'function') {
8852
8597
  return target[key].bind(target);
8853
8598
  }
8854
- // Appwrite sources intentionally skip the client-side
8855
- // $query attachment (see attachArrayMethods comment:
8856
- // "Appwrite sources will get their $query from the
8857
- // Appwrite plugin"). Delegate to the Appwrite methods
8858
- // handler so the click hits the backend instead of
8859
- // silently falling through to `undefined` or, in some
8860
- // proxy paths, a no-op `() => []`. Without this, demo
8861
- // sort/query buttons appear to do nothing — no console
8862
- // error, no network request — because the call resolves
8863
- // to the chaining fallback's stub `queryFn`.
8599
+ // Appwrite sources skip client-side $query attachment;
8600
+ // delegate to the Appwrite handler so the call hits the
8601
+ // backend instead of the no-op chaining fallback.
8864
8602
  if (key === '$query' || key === '$search') {
8865
8603
  const createAppwriteMethodsHandler = window.ManifestDataProxiesAppwrite?.createAppwriteMethodsHandler;
8866
8604
  if (createAppwriteMethodsHandler) {
@@ -8920,33 +8658,24 @@ function registerXMagicMethod(loadDataSource) {
8920
8658
  }
8921
8659
  }
8922
8660
 
8923
- // For non-arrays (objects), check if they contain nested arrays
8661
+ // Objects: cache the nested proxy per source so Alpine
8662
+ // re-evaluations get the same instance (a fresh proxy each
8663
+ // time looks "new" and re-triggers evaluation → infinite loop).
8924
8664
  if (value && typeof value === 'object' && !Array.isArray(value) && value !== null) {
8925
- // Use nested object proxy to handle arrays within objects
8926
- // CRITICAL: Cache nested proxies at $x level to prevent infinite loops
8927
- // When Alpine re-evaluates expressions, it accesses $x.example again
8928
- // If we create a new proxy each time, Alpine sees it as a "new" object and re-evaluates again
8929
8665
  if (!window.ManifestDataProxiesCore.nestedDataSourceProxyCache) {
8930
8666
  window.ManifestDataProxiesCore.nestedDataSourceProxyCache = new Map();
8931
8667
  }
8932
8668
  const nestedCache = window.ManifestDataProxiesCore.nestedDataSourceProxyCache;
8933
8669
 
8934
- // Check cache first - use data source name as key
8935
- // CRITICAL: Always return cached proxy if it exists to prevent infinite loops
8936
- // When Alpine re-evaluates expressions, it accesses $x.example again
8937
- // If we return the same proxy instance, Alpine won't see it as "new" and won't re-evaluate
8938
- // NOTE: This cache check is redundant now (we check earlier), but keeping for safety
8670
+ // Redundant with the earlier check, kept for safety
8939
8671
  if (nestedCache.has(prop)) {
8940
8672
  const cachedProxy = nestedCache.get(prop);
8941
8673
  if (cachedProxy) {
8942
8674
  activeProps.delete(propKey);
8943
8675
  return cachedProxy;
8944
- } else {
8945
8676
  }
8946
- } else {
8947
8677
  }
8948
8678
 
8949
- // Clear cache entry if it exists but is invalid (safety check)
8950
8679
  if (nestedCache.has(prop) && !nestedCache.get(prop)) {
8951
8680
  nestedCache.delete(prop);
8952
8681
  }
@@ -8954,10 +8683,8 @@ function registerXMagicMethod(loadDataSource) {
8954
8683
  const createNestedObjectProxy = window.ManifestDataProxies?.createNestedObjectProxy;
8955
8684
  if (createNestedObjectProxy) {
8956
8685
 
8957
- // CRITICAL: MUST use rawValue, never value (Alpine-wrapped)
8958
- // Using Alpine-wrapped value causes infinite recursion when Alpine wraps our proxy
8959
- // and accesses properties (e.g. :aria-label="$x.content.theme.light"), triggering
8960
- // reactivity that re-evaluates the expression and re-enters this get → stack overflow.
8686
+ // MUST build from rawValue, never the Alpine-wrapped value —
8687
+ // wrapping our proxy and reading it re-triggers reactivity overflow.
8961
8688
  if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
8962
8689
  activeProps.delete(propKey);
8963
8690
  const createLoadingProxy = window.ManifestDataProxiesCore?.createLoadingProxy;
@@ -8976,21 +8703,15 @@ function registerXMagicMethod(loadDataSource) {
8976
8703
  return rawValue;
8977
8704
  }
8978
8705
 
8979
- // Cache the nested proxy - this prevents creating new proxies on each access
8980
8706
  if (nestedProxy) {
8981
8707
  nestedCache.set(prop, nestedProxy);
8982
8708
  } else {
8983
- // If nested proxy creation failed, return raw value as fallback
8984
8709
  activeProps.delete(propKey);
8985
8710
  return rawValue;
8986
8711
  }
8987
8712
 
8988
- // CRITICAL: Remove from activeProps AFTER caching but BEFORE returning
8989
- // This ensures the proxy is cached before Alpine can trigger another access
8713
+ // Clear activeProps after caching, before returning
8990
8714
  activeProps.delete(propKey);
8991
-
8992
- // CRITICAL: Return the cached proxy immediately
8993
- // Don't do anything else that might trigger Alpine reactivity
8994
8715
  return nestedProxy;
8995
8716
  }
8996
8717
  }
@@ -9084,19 +8805,16 @@ function registerXMagicMethod(loadDataSource) {
9084
8805
  });
9085
8806
  pendingLoads.set(prop, loadPromise);
9086
8807
 
9087
- // For array data sources, return an empty array with methods attached
9088
- // This allows .map(), .filter(), etc. to work even before data loads
9089
- // CRITICAL: Use attachArrayMethods to attach methods directly to the array
9090
- // This ensures Alpine can see them even when it wraps the array
8808
+ // Array sources: empty array with methods attached, so
8809
+ // .map()/.filter() work before data loads (and Alpine sees them).
9091
8810
  if (isArrayDataSource) {
9092
8811
  const emptyArray = [];
9093
8812
  const attachArrayMethods = window.ManifestDataProxies?.attachArrayMethods;
9094
8813
  if (attachArrayMethods) {
9095
8814
  const arrayWithMethods = attachArrayMethods(emptyArray, prop, reloadDataSource);
9096
- // CRITICAL: Wrap in a proxy with has() trap so Alpine can see $search, $query, etc.
8815
+ // has() trap so Alpine sees $search/$query/etc.
9097
8816
  return new Proxy(arrayWithMethods, {
9098
8817
  get(target, key) {
9099
- // CRITICAL: Explicitly handle base plugin methods first
9100
8818
  if (key === '$search' || key === '$query' || key === '$route') {
9101
8819
  if (key in target && typeof target[key] === 'function') {
9102
8820
  return target[key].bind(target);
@@ -9183,19 +8901,15 @@ function registerXMagicMethod(loadDataSource) {
9183
8901
  const attachArrayMethods = window.ManifestDataProxies?.attachArrayMethods;
9184
8902
  if (attachArrayMethods) {
9185
8903
  const arrayWithMethods = attachArrayMethods(value, prop, loadDataSource);
9186
- // CRITICAL: Wrap in a proxy with has() trap so Alpine can see $search, $query, etc.
8904
+ // has() trap so Alpine sees $search/$query/etc.
9187
8905
  return new Proxy(arrayWithMethods, {
9188
8906
  get(target, key) {
9189
- // Handle base plugin methods with fallbacks
9190
8907
  if (key === '$search' || key === '$query') {
9191
8908
  if (target && typeof target === 'object' && key in target && typeof target[key] === 'function') {
9192
8909
  return target[key].bind(target);
9193
8910
  }
9194
- // Appwrite-source delegation: $query is intentionally
9195
- // not attached to Appwrite arrays by attachArrayMethods
9196
- // (it requires a backend round-trip). Route to the
9197
- // Appwrite methods handler instead of the no-op fallback
9198
- // so sort/query/search buttons actually fire requests.
8911
+ // Appwrite $query isn't attached (needs a backend round-trip);
8912
+ // route to the Appwrite handler so buttons actually fire requests.
9199
8913
  const createAppwriteMethodsHandler = window.ManifestDataProxiesAppwrite?.createAppwriteMethodsHandler;
9200
8914
  if (createAppwriteMethodsHandler) {
9201
8915
  try {
@@ -9268,15 +8982,13 @@ function registerXMagicMethod(loadDataSource) {
9268
8982
  }
9269
8983
  return value;
9270
8984
 
9271
- // Create data source proxy for arrays
8985
+ // NOTE: unreachable below (early return above); kept for reference.
9272
8986
  const dataSourceProxy = new Proxy(value, {
9273
8987
  get(target, key) {
9274
- // Handle special keys
9275
8988
  if (key === 'then' || key === 'catch' || key === 'finally') {
9276
8989
  return undefined;
9277
8990
  }
9278
8991
 
9279
- // Handle state properties
9280
8992
  const stateHandler = window.ManifestDataProxiesMagic?.createStatePropertyHandler;
9281
8993
  if (stateHandler) {
9282
8994
  const stateValue = stateHandler(prop)(key);
@@ -9632,9 +9344,8 @@ function registerXMagicMethod(loadDataSource) {
9632
9344
  const createLoadingProxy = window.ManifestDataProxiesCore?.createLoadingProxy;
9633
9345
  return createLoadingProxy ? createLoadingProxy(prop) : {};
9634
9346
  } finally {
9635
- // Always remove so the same prop can be read again (e.g. Alpine re-evaluating $x.products).
9636
- // Without this, NO_STORE_DATA and other paths left prop in activeProps and the next
9637
- // get(proxy, prop) was wrongly treated as CIRCULAR.
9347
+ // Always clear so the same prop can be read again (else Alpine's
9348
+ // next read of it is wrongly treated as circular).
9638
9349
  activeProps.delete(propKey);
9639
9350
  }
9640
9351
  } finally {
@@ -9672,8 +9383,7 @@ function registerXMagicMethod(loadDataSource) {
9672
9383
  }
9673
9384
  }
9674
9385
 
9675
- // Register $try magic method for cleaner async error handling
9676
- // Usage: $try(() => $x.assets.$removeFrom(...), 'assetsError')
9386
+ // $try magic: async error handling $try(() => $x.assets.$removeFrom(...), 'assetsError')
9677
9387
  if (typeof Alpine !== 'undefined' && typeof Alpine.magic === 'function') {
9678
9388
  if (!window.__manifestTryMagicRegistered) {
9679
9389
  window.__manifestTryMagicRegistered = true;
@@ -9695,15 +9405,12 @@ if (typeof Alpine !== 'undefined' && typeof Alpine.magic === 'function') {
9695
9405
  scope[errorVar] = error.message || 'Operation failed';
9696
9406
  }
9697
9407
  }
9698
- // Don't throw - return undefined on error so caller can handle gracefully
9408
+ // Return undefined on error so the caller can handle it gracefully
9699
9409
  return undefined;
9700
9410
  }
9701
9411
  };
9702
9412
  });
9703
9413
  }
9704
-
9705
- // Note: window.$x getter is defined at module load time (top of file)
9706
- // so it's available immediately, even before registerXMagicMethod is called
9707
9414
  }
9708
9415
 
9709
9416
  // Clear nested proxy cache for a specific data source (called when store updates)
@@ -9712,7 +9419,7 @@ function clearNestedProxyCacheForDataSource(dataSourceName) {
9712
9419
  window.ManifestDataProxiesCore.nestedDataSourceProxyCache.delete(dataSourceName);
9713
9420
  }
9714
9421
 
9715
- // Export function to window for use by other subscripts
9422
+ // Exports
9716
9423
  if (!window.ManifestDataProxies) {
9717
9424
  window.ManifestDataProxies = {};
9718
9425
  }
@@ -9720,10 +9427,9 @@ window.ManifestDataProxies.registerXMagicMethod = registerXMagicMethod;
9720
9427
  window.ManifestDataProxies.clearNestedProxyCacheForDataSource = clearNestedProxyCacheForDataSource;
9721
9428
 
9722
9429
  /* Manifest Data Sources - Directives */
9723
- // Register x-files directive for automatic file management (files linked to table entries)
9430
+ // x-files / x-data-files: reactive file arrays for a table entry
9724
9431
  function registerFilesDirective() {
9725
9432
  if (typeof Alpine === 'undefined') {
9726
- // Wait for Alpine to be available
9727
9433
  const checkAlpine = setInterval(() => {
9728
9434
  if (typeof Alpine !== 'undefined') {
9729
9435
  clearInterval(checkAlpine);
@@ -9734,12 +9440,10 @@ function registerFilesDirective() {
9734
9440
  return;
9735
9441
  }
9736
9442
 
9737
- // Helper to walk up DOM tree to find parent x-for template
9443
+ // Walk up to the nearest parent x-for template
9738
9444
  function findParentXFor(el) {
9739
9445
  let current = el;
9740
- // Walk up the tree, checking each element
9741
9446
  while (current) {
9742
- // Check if current element is a template with x-for
9743
9447
  if (current.tagName === 'TEMPLATE' && current.hasAttribute('x-for')) {
9744
9448
  return current;
9745
9449
  }
@@ -9752,17 +9456,15 @@ function registerFilesDirective() {
9752
9456
  return null;
9753
9457
  }
9754
9458
 
9755
- // Helper to extract loop item variable name from x-for expression
9459
+ // "item in $x.source" -> "item"
9756
9460
  function extractLoopItemName(xForExpression) {
9757
- // x-for="item in $x.source" -> "item"
9758
9461
  const match = xForExpression.match(/^(\w+)\s+in\s+/);
9759
9462
  return match ? match[1] : null;
9760
9463
  }
9761
9464
 
9762
9465
  Alpine.directive('files', (el, { expression, modifiers }, { effect, evaluateLater, cleanup }) => {
9763
9466
 
9764
- // For string literals (data source names), we need to handle them specially
9765
- // If expression is a plain identifier (no quotes, no dots, no special chars), treat as string
9467
+ // A bare identifier is a data-source name (string literal), not an expression
9766
9468
  const isPlainIdentifier = expression && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(expression.trim());
9767
9469
 
9768
9470
  const evaluate = isPlainIdentifier
@@ -9789,19 +9491,16 @@ function registerFilesDirective() {
9789
9491
  let isProcessing = false; // Guard to prevent infinite loops
9790
9492
  let waitingForDataSource = null; // Track which data source we're waiting for
9791
9493
 
9792
- // Initialize reactive state in Alpine scope IMMEDIATELY (before Alpine evaluates expressions)
9793
- // This must happen synchronously, not in an effect, so Alpine can find the variables
9494
+ // Seed scope state synchronously (before Alpine evaluates expressions),
9495
+ // not in an effect, so the variables exist on first evaluation.
9794
9496
  let scope;
9795
9497
  try {
9796
9498
  scope = Alpine.$data(el);
9797
9499
  } catch (e) {
9798
- // Scope might not be ready yet, will initialize in effect
9799
- scope = null;
9500
+ scope = null; // not ready seed in the effect instead
9800
9501
  }
9801
9502
 
9802
9503
  if (scope) {
9803
- // Always initialize - don't check for undefined, just set them
9804
- // This ensures they exist when Alpine first evaluates expressions
9805
9504
  if (!('files' in scope)) {
9806
9505
  scope.files = [];
9807
9506
  }
@@ -10201,12 +9900,8 @@ function registerFilesDirective() {
10201
9900
  });
10202
9901
  });
10203
9902
 
10204
- // Register x-project-files directive - simplified, turnkey solution for project files
10205
- // Usage: <div x-project-files="project"> - automatically provides files, loadingFiles, filesError
10206
- // Register x-entry-files directive - generic directive for displaying files linked to any table entry
10207
- // Usage: <div x-entry-files="entry"> - automatically provides files, loadingFiles, filesError
10208
- // Renamed from x-project-files to be more generic (works with any table entry, not just projects)
10209
- // WeakMap to store namespace per element - ensures complete isolation
9903
+ // x-data-files="entry": provides files/loadingFiles/filesError for a table entry.
9904
+ // Per-element namespace via WeakMap for full isolation.
10210
9905
  const dataFilesNamespaces = new WeakMap();
10211
9906
 
10212
9907
  Alpine.directive('data-files', (el, { expression }, { effect, evaluateLater, cleanup }) => {
@@ -10226,10 +9921,9 @@ function registerFilesDirective() {
10226
9921
 
10227
9922
  const directiveInstanceId = `directive-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
10228
9923
 
10229
- // Isolated reactive scope for this directive instance. Alpine never
10230
- // re-reads x-data attributes on initialized elements, so editing the
10231
- // attribute would resolve $data(el) to the ANCESTOR scope and pollute
10232
- // it — addScopeToNode attaches a fresh scope layer to this node.
9924
+ // Isolated reactive scope layer for this instance. Editing x-data won't
9925
+ // work (Alpine never re-reads it on initialized nodes) and would pollute
9926
+ // the ancestor scope addScopeToNode attaches a fresh layer instead.
10233
9927
  const isolatedData = Alpine.reactive({
10234
9928
  files: [],
10235
9929
  loadingFiles: false,
@@ -10238,8 +9932,7 @@ function registerFilesDirective() {
10238
9932
  const removeIsolatedScope = Alpine.addScopeToNode(el, isolatedData);
10239
9933
  cleanupCallbacks.push(removeIsolatedScope);
10240
9934
 
10241
- // Merged scope view: writes to files/loadingFiles/filesError land on
10242
- // isolatedData (top of stack), magics like $watch resolve from ancestors
9935
+ // Merged view: files/* writes hit isolatedData (top of stack); $watch etc. resolve from ancestors
10243
9936
  const scope = Alpine.$data(el);
10244
9937
 
10245
9938
  // Store reference in WeakMap for access in closures
@@ -10395,17 +10088,13 @@ function registerFilesDirective() {
10395
10088
  // CRITICAL DEBUG: Verify we're using the correct projectId before calling getFilesForEntry
10396
10089
  const loadedFiles = await getFilesForEntry('projects', currentProjectId, bucketId, 'fileIds') || [];
10397
10090
 
10398
- // CRITICAL: Always create a NEW array for scope.files to avoid sharing between directive instances
10399
- // Don't mutate the existing array - create a completely new one
10091
+ // Fresh array so instances don't share references
10400
10092
  const newFilesArray = [...loadedFiles];
10401
10093
 
10402
- // Update local reference
10403
10094
  files = newFilesArray;
10404
10095
 
10405
- // Update both namespace and scope directly (they reference the same arrays/values)
10406
10096
  const ns = getNamespace();
10407
10097
  if (ns) {
10408
- // DIAGNOSTIC: Mark each file with the project ID for tracking
10409
10098
  const markedFiles = newFilesArray.map(file => ({
10410
10099
  ...file,
10411
10100
  _debugProjectId: currentProjectId,
@@ -10433,13 +10122,11 @@ function registerFilesDirective() {
10433
10122
  }
10434
10123
 
10435
10124
 
10436
- // Update lastFileIds to match what we actually loaded (from Appwrite)
10437
- // This is more reliable than reading from the store, which might be stale
10125
+ // Track what we actually loaded (Appwrite is truth, store may be stale)
10438
10126
  const loadedFileIdsArray = loadedFiles.map(f => f.$id) || [];
10439
10127
  lastFileIds = JSON.stringify(loadedFileIdsArray);
10440
10128
 
10441
- // CRITICAL: Sync store AND Appwrite database's fileIds with what actually exists
10442
- // If the store/database has stale fileIds (file IDs that don't exist), clean them up
10129
+ // Reconcile stale fileIds in both the store and the Appwrite row
10443
10130
  const store = Alpine.store('data');
10444
10131
  const projects = store?.projects;
10445
10132
  if (Array.isArray(projects)) {
@@ -10478,8 +10165,8 @@ function registerFilesDirective() {
10478
10165
  }
10479
10166
  }
10480
10167
 
10481
- // CRITICAL: Only update Appwrite database if it's actually stale
10482
- // This prevents unnecessary realtime events that might overwrite our store update
10168
+ // Only write the DB if actually stale — avoids a
10169
+ // realtime event that could overwrite our store update
10483
10170
  if (databaseIsStale) {
10484
10171
  try {
10485
10172
  const manifest = await window.ManifestDataConfig?.ensureManifest?.();
@@ -10549,10 +10236,7 @@ function registerFilesDirective() {
10549
10236
  let fileIdsWatchUnwatch = null;
10550
10237
 
10551
10238
  effect(() => {
10552
- // Evaluate project expression - this will re-run when project changes
10553
10239
  evaluateEntry((value) => {
10554
- // Always get the latest project reference from the store by ID
10555
- // This prevents using stale references when the projects array updates
10556
10240
  const currentProjectId = value?.$id;
10557
10241
 
10558
10242
  if (!currentProjectId) {
@@ -10602,13 +10286,11 @@ function registerFilesDirective() {
10602
10286
  loadProjectFiles();
10603
10287
  }
10604
10288
 
10605
- // Watch for fileIds changes by watching the store directly
10606
- // This ensures we always get the latest project reference from the store
10607
- // Only create watch if we don't already have one for this project ID AND haven't created one yet
10289
+ // Watch this project's fileIds via the store (one watch per project id)
10608
10290
  if (currentProjectId && scope && typeof scope.$watch === 'function' && !fileIdsWatchUnwatch && !watchCreated) {
10609
- watchCreated = true; // Mark watch as created to prevent duplicates
10610
- const projectId = currentProjectId; // Capture project ID for closure
10611
- let isProcessing = false; // Guard against multiple simultaneous updates
10291
+ watchCreated = true;
10292
+ const projectId = currentProjectId; // capture for closure
10293
+ let isProcessing = false;
10612
10294
 
10613
10295
  // Initialize lastFileIds with current value from the store to prevent false positives
10614
10296
  const store = Alpine.store('data');
@@ -10620,23 +10302,17 @@ function registerFilesDirective() {
10620
10302
  }
10621
10303
  }
10622
10304
 
10623
- // Use function-based watch that accesses the store - Alpine will track this
10624
- // This is more reliable than string expressions for nested store access
10625
- // IMPORTANT: Only access the specific project's fileIds to minimize reactivity
10305
+ // Function watch (more reliable than a string expr for nested store access);
10306
+ // reads only this project's fileIds to keep reactivity narrow.
10626
10307
  fileIdsWatchUnwatch = scope.$watch(
10627
10308
  () => {
10628
- // Access the store - Alpine tracks this
10629
10309
  const store = Alpine.store('data');
10630
10310
  const projects = store?.projects;
10631
10311
  if (!Array.isArray(projects)) return null;
10632
10312
 
10633
- // Find the current project by ID - always gets latest reference
10634
10313
  const currentProject = projects.find(p => p.$id === projectId);
10635
10314
  if (!currentProject) return null;
10636
10315
 
10637
- // Return the fileIds array as JSON string for comparison
10638
- // Accessing fileIds here makes Alpine track changes to this property
10639
- // This should only trigger when THIS project's fileIds changes
10640
10316
  return JSON.stringify(currentProject.fileIds || []);
10641
10317
  },
10642
10318
  (currentFileIdsJson) => {
@@ -10703,16 +10379,13 @@ function registerFilesDirective() {
10703
10379
  };
10704
10380
 
10705
10381
  const handleFileCreated = (e) => {
10706
- // Check if the file is linked to this project
10707
10382
  const fileId = e.detail?.fileId;
10708
10383
  const entryId = e.detail?.entryId;
10709
10384
  const tableName = e.detail?.tableName;
10710
10385
 
10711
- // CRITICAL: Use entryId from event to identify target project
10712
- // This is more reliable than checking the store, which might be stale
10386
+ // Match on the event's entryId (store may be stale)
10713
10387
  const matchesProject = entryId === projectId && tableName === 'projects';
10714
10388
 
10715
- // Use entryId from event instead of checking store (which is stale)
10716
10389
  if (fileId && matchesProject && !eventProcessing) {
10717
10390
  eventProcessing = true;
10718
10391
  // Reload files for this project - getFilesForEntry will get the latest from Appwrite
@@ -10764,8 +10437,7 @@ function registerFilesDirective() {
10764
10437
  });
10765
10438
  }
10766
10439
 
10767
- // Directive removed - using project.$files property instead
10768
- // Auto-register directive when Alpine is ready
10440
+ // Auto-registration handled elsewhere; kept for reference.
10769
10441
  // if (typeof document !== 'undefined') {
10770
10442
  // if (typeof Alpine !== 'undefined') {
10771
10443
  // registerFilesDirective();
@@ -10778,9 +10450,8 @@ function registerFilesDirective() {
10778
10450
 
10779
10451
  /* Manifest Data Sources - Main Initialization */
10780
10452
 
10781
- // Filter storage files by scope (client-side filtering)
10782
- // Appwrite returns all files user has access to, but we need to filter by current team
10783
- // to match database team scope behavior
10453
+ // Client-side scope filter: Appwrite returns all accessible files, so narrow to
10454
+ // the current team to match database team-scope behavior.
10784
10455
  async function filterFilesByScope(files, scope) {
10785
10456
  if (!files || !Array.isArray(files) || files.length === 0) {
10786
10457
  return files;
@@ -11035,26 +10706,20 @@ async function handleStorageRealtimeEvent(dataSourceName, bucketId, scope, event
11035
10706
  }
11036
10707
  }
11037
10708
 
11038
- // Track recently processed events to prevent duplicate processing
11039
- // Use a combination of event type, ID, and timestamp to create a unique key per client
10709
+ // Realtime event dedup: key by type+id+sequence(+timestamp)
11040
10710
  const processedEvents = new Map(); // Map<eventKey, timestamp>
11041
- const EVENT_DEDUP_WINDOW = 2000; // 2 seconds (reduced from 5 to catch rapid duplicates but allow legitimate updates)
10711
+ const EVENT_DEDUP_WINDOW = 2000; // ms
11042
10712
 
11043
- // Generate a unique key for an event
11044
- // For updates, we need to be more careful - use timestamp to allow same sequence from different sources
11045
10713
  function getEventKey(eventType, payload) {
11046
10714
  const id = payload?.$id || payload?.row?.$id || payload?.rowId || payload?.id || payload;
11047
10715
  const sequence = payload?.$sequence || payload?.sequence;
11048
10716
  const timestamp = payload?.$updatedAt || payload?.$createdAt || payload?.timestamp;
11049
10717
 
11050
- // For updates, include timestamp to allow processing updates with same sequence but different times
11051
- // This handles the case where multiple clients receive the same sequence number
10718
+ // Updates add timestamp so the same sequence from different clients isn't dropped
11052
10719
  if (eventType === 'update' && timestamp) {
11053
- // Use a combination that allows same sequence from different times
11054
10720
  return `${eventType}:${id}:${sequence}:${timestamp}`;
11055
10721
  }
11056
10722
 
11057
- // For create/delete, sequence is usually unique enough
11058
10723
  return `${eventType}:${id}:${sequence || Date.now()}`;
11059
10724
  }
11060
10725
 
@@ -11155,8 +10820,8 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
11155
10820
  // Check if row still matches scope after update
11156
10821
  const rowMatchesScope = await checkRowMatchesScope(row, scope);
11157
10822
  if (rowMatchesScope) {
11158
- // For most data sources (roles, etc.), always update on realtime events
11159
- // Special handling only for projects with fileIds to protect optimistic updates
10823
+ // Most sources update unconditionally; projects-with-fileIds get
10824
+ // special handling to protect optimistic uploads (see below).
11160
10825
  const existingFileIds = existingRow?.fileIds || [];
11161
10826
  const incomingFileIds = row?.fileIds || [];
11162
10827
  const hasFileIds = existingFileIds.length > 0 || incomingFileIds.length > 0;
@@ -11164,41 +10829,30 @@ async function handleTableRealtimeEvent(dataSourceName, databaseId, tableId, sco
11164
10829
 
11165
10830
  let shouldUpdate = true;
11166
10831
 
11167
- // Special handling only for projects with fileIds
11168
10832
  if (isProjectWithFiles) {
11169
10833
  const existingUpdatedAt = existingRow?.$updatedAt || existingRow?.$sequence;
11170
10834
  const newUpdatedAt = row?.$updatedAt || row?.$sequence;
11171
10835
  const shouldUpdateByTimestamp = !newUpdatedAt || !existingUpdatedAt || newUpdatedAt !== existingUpdatedAt;
11172
10836
 
11173
10837
  if (!shouldUpdateByTimestamp) {
11174
- // Timestamps match and both exist - skip update
11175
- shouldUpdate = false;
10838
+ shouldUpdate = false; // identical timestamps → no-op
11176
10839
  } else {
11177
10840
  const existingFileIdsSet = new Set(existingFileIds);
11178
10841
  const incomingFileIdsSet = new Set(incomingFileIds);
11179
10842
 
11180
- // Check if incoming is a superset (has all existing files + more)
11181
10843
  const isSuperset = incomingFileIds.every(id => existingFileIdsSet.has(id)) &&
11182
10844
  incomingFileIds.length > existingFileIds.length;
11183
10845
 
11184
- // Check if incoming is missing files that exist in current (stale data)
11185
10846
  const isMissingFiles = existingFileIds.some(id => !incomingFileIdsSet.has(id));
11186
10847
 
11187
- // Only update if:
11188
- // 1. Incoming is a superset (has all existing + more), OR
11189
- // 2. Incoming is equal (same files), OR
11190
- // 3. Timestamp comparison suggests it's definitely newer (more than 1 second difference)
11191
10848
  const timestampDiff = newUpdatedAt > existingUpdatedAt ?
11192
10849
  (new Date(newUpdatedAt) - new Date(existingUpdatedAt)) :
11193
10850
  (new Date(existingUpdatedAt) - new Date(newUpdatedAt));
11194
- const isDefinitelyNewer = timestampDiff > 1000; // More than 1 second difference
10851
+ const isDefinitelyNewer = timestampDiff > 1000; // ms
11195
10852
 
11196
- // CRITICAL: If existing has more fileIds than incoming, and timestamps are close,
11197
- // this is likely a stale realtime event overwriting an optimistic update
11198
- // Protect optimistic updates by requiring incoming to be a superset or definitely newer
10853
+ // Incoming missing files + not clearly newer = stale event
10854
+ // racing an optimistic update; ignore it to protect the upload.
11199
10855
  if (isMissingFiles && !isDefinitelyNewer) {
11200
- // Incoming data is missing files and timestamp isn't definitely newer - likely stale
11201
- // This protects optimistic updates from being overwritten by stale realtime events
11202
10856
  console.warn('[Realtime] Ignoring stale realtime update (protecting optimistic update):', {
11203
10857
  projectId: row.$id,
11204
10858
  existingFileIds: existingFileIds,
@@ -11447,13 +11101,9 @@ async function loadDataSource(dataSourceName, locale = 'en') {
11447
11101
  const cacheKey = `${dataSourceName}:${locale}`;
11448
11102
  const { dataSourceCache, loadingPromises, isInitializing, updateStore } = window.ManifestDataStore;
11449
11103
 
11450
- // Check memory cache first. The store write is guarded against stale
11451
- // locales: a caller that resolved its locale before a switch (or an effect
11452
- // re-running mid-switch) can request `name:en` after the locale-change
11453
- // reload already wrote `name:fr` — serving the cached data is fine, but
11454
- // writing it to the live store would clobber the current locale. Localized
11455
- // data carries a `_locale` stamp; unstamped (non-localized) data writes
11456
- // unconditionally.
11104
+ // Memory cache. Serving cached data is fine, but writing an old locale to the
11105
+ // live store would clobber a concurrent locale switch so guard the store
11106
+ // write via the `_locale` stamp (unstamped/non-localized data writes freely).
11457
11107
  if (dataSourceCache.has(cacheKey)) {
11458
11108
  const cachedData = dataSourceCache.get(cacheKey);
11459
11109
  const liveLocale = (window.Alpine && Alpine.store('locale')?.current)
@@ -11724,16 +11374,13 @@ async function loadDataSource(dataSourceName, locale = 'en') {
11724
11374
  enhancedData = [];
11725
11375
  }
11726
11376
 
11727
- // Update cache (store unsealed version for our use).
11728
- // Always safe — the cache key carries the locale this load was for.
11377
+ // Cache is always safe the key carries this load's locale.
11729
11378
  dataSourceCache.set(cacheKey, enhancedData);
11730
11379
 
11731
- // Stale-locale guard: if the app's locale changed while this load was
11732
- // in flight, a LOCALIZED source's result is stale the locale-change
11733
- // listener has already reloaded (or is reloading) the right locale,
11734
- // and writing this one would clobber it. Non-localized sources are
11735
- // locale-independent and must still write (the listener never reloads
11736
- // them, so skipping would orphan an initial load).
11380
+ // Stale-locale guard: if the locale changed mid-load, a localized
11381
+ // source's result is stale (the locale listener already reloaded the
11382
+ // right one). Non-localized sources still write the listener never
11383
+ // reloads them, so skipping would orphan the initial load.
11737
11384
  const localeSensitive = !!(dataSource && typeof dataSource === 'object'
11738
11385
  && (dataSource.locales || dataSource[locale]));
11739
11386
  const liveLocale = (window.Alpine && Alpine.store('locale')?.current)
@@ -11783,8 +11430,7 @@ async function loadDataSource(dataSourceName, locale = 'en') {
11783
11430
  function setupUrlChangeListeners() {
11784
11431
  let currentUrl = window.location.pathname;
11785
11432
 
11786
- // Create a reactive object for route tracking that Alpine can track
11787
- // This is separate from the store to ensure Alpine tracks it properly
11433
+ // Separate reactive object for route tracking (Alpine tracks it reliably)
11788
11434
  const routeTracker = Alpine.reactive ? Alpine.reactive({
11789
11435
  currentUrl: window.location.pathname
11790
11436
  }) : { currentUrl: window.location.pathname };
@@ -11800,22 +11446,16 @@ function setupUrlChangeListeners() {
11800
11446
  currentUrl = newUrl;
11801
11447
  const store = Alpine.store('data');
11802
11448
  if (store && store._initialized) {
11803
- // Update the store property - Alpine stores are reactive by default
11804
- // CRITICAL: Use Object.assign or spread to ensure Alpine tracks the change
11805
- // Direct property assignment might not trigger reactivity in all cases
11449
+ // Use Object.assign so Alpine reliably tracks the change
11806
11450
  Object.assign(store, { _currentUrl: newUrl });
11807
11451
 
11808
- // CRITICAL: Also update the reactive route tracker
11809
- // This ensures Alpine tracks the change even if store access isn't tracked in Proxy get traps
11452
+ // Also update the reactive tracker (covers untracked store get traps)
11810
11453
  if (routeTracker) {
11811
11454
  routeTracker.currentUrl = newUrl;
11812
11455
  }
11813
11456
 
11814
- // Force Alpine to recognize the change by accessing the property
11815
- // This ensures Alpine's reactivity system tracks the update
11816
- const _ = store._currentUrl;
11817
- // Dispatch a custom event to ensure any components listening for URL changes can react
11818
- // This helps with reactivity in cases where Alpine's automatic tracking might miss the update
11457
+ const _ = store._currentUrl; // touch to force tracking
11458
+ // Event fallback for cases Alpine's auto-tracking might miss
11819
11459
  try {
11820
11460
  window.dispatchEvent(new CustomEvent('manifest:data-url-change', {
11821
11461
  detail: { url: newUrl }
@@ -11836,7 +11476,7 @@ function setupUrlChangeListeners() {
11836
11476
  window.addEventListener('manifest:route-change', (event) => {
11837
11477
  const newUrl = event.detail?.to ?? window.ManifestRoutingNavigation?.getCurrentRoute?.() ?? window.location.pathname;
11838
11478
  updateCurrentUrl(newUrl);
11839
- // Flush route proxies after other listeners have queued their updates, so $x.*.$route('path') content updates without refresh
11479
+ // Flush route proxies after other listeners queue updates, so $x.*.$route() refreshes in place
11840
11480
  setTimeout(() => {
11841
11481
  if (window.ManifestDataRouteProxyUpdateQueue?.flushSync) {
11842
11482
  window.ManifestDataRouteProxyUpdateQueue.flushSync();
@@ -11952,24 +11592,11 @@ async function initializeDataSourcesPlugin() {
11952
11592
  _currentUrl: existingStore._currentUrl || window.location.pathname
11953
11593
  });
11954
11594
 
11955
- // Pre-load all local file-backed data sources so $x.* accessors see
11956
- // real data on the very first render pass.
11957
- //
11958
- // Each source is at most ONE fetch regardless of type:
11959
- // - Simple string paths (e.g. "/data/clients.yaml") one file.
11960
- // - Localized objects (e.g. { "en": "...", "fr": "..." }) → only the
11961
- // current locale file is fetched (+ default locale for fallback
11962
- // merging if different), NOT all 35 variants.
11963
- // - Single CSV with embedded locales → one file.
11964
- //
11965
- // Skipped (remain on-demand):
11966
- // - Appwrite collections / buckets — require auth/session context.
11967
- // - API-URL sources — may have side-effects or auth requirements.
11968
- // - The special "manifest" key — handled separately below.
11969
- //
11970
- // Without pre-loading, sources like $x.clients load asynchronously on
11971
- // first access, causing a visible flash in the SPA and missing data in
11972
- // prerender snapshots.
11595
+ // Pre-load local file-backed sources so $x.* has real data on the first
11596
+ // render pass (avoids SPA flash + missing prerender data). At most one
11597
+ // fetch each (localized objects load only the current + default locale).
11598
+ // Skipped/on-demand: Appwrite (needs auth), API-URL (side-effects), and
11599
+ // the special "manifest" key (handled separately below).
11973
11600
  try {
11974
11601
  const manifest = await window.ManifestDataConfig.ensureManifest();
11975
11602
  const locale = (typeof document !== 'undefined' && document.documentElement?.lang) || (typeof Alpine !== 'undefined' && Alpine.store('locale')?.current) || 'en';