mnfst 0.5.199-next.3 → 0.5.199-next.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.199-next.3';
5
+ const MANIFEST_BUILD_VERSION = '0.5.199-next.5';
6
6
 
7
7
  /* Manifest Data Sources - Appwrite Integration */
8
8
 
@@ -121,10 +121,20 @@
121
121
  state.resetQueued = true;
122
122
  queueMicrotask(() => { state.resetQueued = false; reset(); });
123
123
  };
124
- window.addEventListener('manifest:persist:scope', queue);
124
+ // pending → resolved at boot is not a switch: keep every window, hydrate the ones still loading, write under the real scope
125
+ window.addEventListener('manifest:persist:scope', (e) => { if (e.detail && e.detail.boot) adoptScope(); else queue(); });
125
126
  for (const type of WIPE_EVENTS) window.addEventListener(type, queue);
126
127
  }
127
128
 
129
+ function adoptScope() {
130
+ state.recent = null; state.indexPromise = null; state.indexDirty = false;
131
+ state.saved.clear(); state.windows.clear();
132
+ for (const [id, h] of state.handles) {
133
+ hydrate(id).then(w => { if (w && h.hydrate) h.hydrate(w.messages); }).catch(noop);
134
+ touch(id);
135
+ }
136
+ }
137
+
128
138
  // Drop every in-memory window and the index; the store's scope prefix already
129
139
  // covers the records. Handles go idle — the app re-opens for the new scope.
130
140
  function reset() {
@@ -143,9 +153,26 @@
143
153
  const r = records();
144
154
  const ok = rec && typeof rec === 'object' && rec.scope === (r ? r.scope() : '') && Array.isArray(rec.recent);
145
155
  state.recent = ok ? rec.recent.filter(id => typeof id === 'string' || typeof id === 'number') : [];
156
+ pruneIndex();
146
157
  return state.recent;
147
158
  }
148
159
 
160
+ // Drop index ids that have no record (entries from before slots were earned on first write)
161
+ function pruneIndex() {
162
+ const r = records(); const ids = state.recent;
163
+ if (!r || !ids || !ids.length) return;
164
+ const gen = state.generation;
165
+ const asked = ids.slice(); // the live array may be reordered by a promote while the read is in flight
166
+ r.get(asked.map(id => r.key('chat', id))).then(got => {
167
+ if (gen !== state.generation || state.recent !== ids || !got) return;
168
+ const found = new Set(asked.filter((id, i) => !!got[i]));
169
+ const keep = ids.filter(id => found.has(id) || (state.handles.get(id) && state.handles.get(id).count() > 0));
170
+ if (keep.length === ids.length) return;
171
+ ids.length = 0; ids.push(...keep);
172
+ state.indexDirty = true; state.pending.set(INDEX, Date.now() + WRITE_DEBOUNCE_MS); schedule();
173
+ }).catch(noop);
174
+ }
175
+
149
176
  function ensureIndex() {
150
177
  if (state.recent) return Promise.resolve(state.recent);
151
178
  if (state.indexPromise) return state.indexPromise;
@@ -589,7 +616,7 @@
589
616
  commit(); commitParticipants(); commitTyping();
590
617
  }
591
618
 
592
- if (P() && !isAggregate && opts.persistWindow !== false) detach = P().attach(conversationId, { snapshot, reset, stale: () => state.stale, count: () => _msgs.length });
619
+ if (P() && !isAggregate && opts.persistWindow !== false) detach = P().attach(conversationId, { snapshot, reset, hydrate: hydrateWindow, stale: () => state.stale, count: () => _msgs.length });
593
620
 
594
621
  async function open() {
595
622
  state.status = 'loading';
package/lib/manifest.d.ts CHANGED
@@ -268,7 +268,7 @@ export type ManifestExport = (opts?: {
268
268
  [extra: string]: unknown;
269
269
  }) => Promise<unknown>;
270
270
 
271
- /** `$device` from the utilities plugin (enriched by the native plugin when present). */
271
+ /** `$device` from the utilities plugin (enriched by the device plugin when present). */
272
272
  export interface ManifestDevice {
273
273
  readonly os: string;
274
274
  readonly touch: boolean;
@@ -278,18 +278,25 @@ export interface ManifestDevice {
278
278
  readonly platform: string;
279
279
  }
280
280
 
281
- /** `$share` from the native plugin — native sheet, web share, or clipboard fallback. */
281
+ /** `$share` from the device plugin — native sheet, web share, or clipboard fallback. */
282
282
  export type ManifestShare = (opts?: { title?: string; text?: string; url?: string }) =>
283
283
  Promise<{ shared: boolean; method: string; cancelled?: boolean }>;
284
284
 
285
- /** `$push` from the native plugin — push/notification permissions and token. */
285
+ /** `$push` from the device plugin — push/notification permissions and token. */
286
286
  export interface ManifestPush {
287
- request(): Promise<string>;
287
+ /** Ask for permission, on your schedule (never prompted on load). */
288
+ request(): Promise<'granted' | 'denied' | 'prompt' | 'unsupported' | string>;
289
+ /** Register with the platform push service (no-op on the web). */
290
+ register(): Promise<unknown>;
291
+ onToken(handler: (token: string) => void): void;
292
+ onReceive(handler: (notification: unknown) => void): void;
293
+ onTap(handler: (notification: unknown) => void): void;
288
294
  readonly permission: string;
295
+ readonly token: string | null;
289
296
  [extra: string]: unknown;
290
297
  }
291
298
 
292
- /** `$secure` from the native plugin — secure key-value storage (native keychain or fallback). */
299
+ /** `$secure` from the device plugin — secure key-value storage (native keychain or fallback). */
293
300
  export interface ManifestSecure {
294
301
  get(key: string): Promise<string | null>;
295
302
  set(key: string, value: string): Promise<void>;
@@ -299,33 +306,36 @@ export interface ManifestSecure {
299
306
  use(adapter: unknown): void;
300
307
  }
301
308
 
302
- /** `$links` from the native plugin — deep/universal link handling. */
309
+ /** `$links` from the device plugin — deep/universal link handling. */
303
310
  export interface ManifestLinks {
304
- on(handler: (url: string) => void): void;
311
+ /** Take over an inbound link: link.url is the full URL, link.path the route it maps to. */
312
+ on(handler: (link: { url: string; path: string }) => void): void;
305
313
  open(url: string): void;
306
314
  readonly last: string | null;
307
315
  }
308
316
 
309
- /** `$app` from the native plugin — foreground/background state. */
317
+ /** `$app` from the device plugin — foreground/background state. */
310
318
  export interface ManifestApp {
311
319
  readonly active: boolean;
312
320
  onChange(handler: (active: boolean) => void): void;
313
321
  }
314
322
 
315
- /** `$haptics` from the native plugin — haptic feedback with web vibration fallback. */
323
+ /** `$haptics` from the device plugin — haptic feedback with web vibration fallback. */
316
324
  export interface ManifestHaptics {
317
- impact(style?: string): Promise<unknown>;
318
- notification(type?: string): Promise<unknown>;
325
+ impact(style?: 'LIGHT' | 'MEDIUM' | 'HEAVY' | string): Promise<unknown>;
326
+ notification(type?: 'SUCCESS' | 'WARNING' | 'ERROR' | string): Promise<unknown>;
327
+ selection(): Promise<unknown>;
328
+ vibrate(ms?: number): Promise<unknown> | unknown;
319
329
  [extra: string]: unknown;
320
330
  }
321
331
 
322
- /** `$biometric` from the native plugin. */
332
+ /** `$biometric` from the device plugin. */
323
333
  export interface ManifestBiometric {
324
334
  available(): Promise<boolean>;
325
335
  verify(opts?: Record<string, unknown>): Promise<unknown>;
326
336
  }
327
337
 
328
- /** `$camera` from the native plugin. */
338
+ /** `$camera` from the device plugin. */
329
339
  export interface ManifestCamera {
330
340
  photo(opts?: Record<string, unknown>): Promise<unknown>;
331
341
  pick(opts?: Record<string, unknown>): Promise<unknown>;
@@ -465,23 +475,23 @@ declare global {
465
475
  const $edit: ManifestEdit;
466
476
  /** Text editor (text-edit plugin). */
467
477
  const $text: ManifestTextEdit;
468
- /** Device/platform info (utilities; enriched by the native plugin). */
478
+ /** Device/platform info (utilities; enriched by the device plugin). */
469
479
  const $device: ManifestDevice;
470
- /** Native share (native plugin). */
480
+ /** Native share (device plugin). */
471
481
  const $share: ManifestShare;
472
- /** Push permissions (native plugin). */
482
+ /** Push permissions (device plugin). */
473
483
  const $push: ManifestPush;
474
- /** Secure storage (native plugin). */
484
+ /** Secure storage (device plugin). */
475
485
  const $secure: ManifestSecure;
476
- /** Deep links (native plugin). */
486
+ /** Deep links (device plugin). */
477
487
  const $links: ManifestLinks;
478
- /** App foreground state (native plugin). */
488
+ /** App foreground state (device plugin). */
479
489
  const $app: ManifestApp;
480
- /** Haptics (native plugin). */
490
+ /** Haptics (device plugin). */
481
491
  const $haptics: ManifestHaptics;
482
- /** Biometric auth (native plugin). */
492
+ /** Biometric auth (device plugin). */
483
493
  const $biometric: ManifestBiometric;
484
- /** Camera (native plugin). */
494
+ /** Camera (device plugin). */
485
495
  const $camera: ManifestCamera;
486
496
 
487
497
  /** Window-level Manifest namespace exposed by the loader. */
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.199-next.3';
5
+ const MANIFEST_BUILD_VERSION = '0.5.199-next.5';
6
6
 
7
7
  /* Manifest Data Sources - Configuration */
8
8
 
@@ -1626,6 +1626,7 @@ window.ManifestDataStore = {
1626
1626
  // Reads `persist` per source and top-level `persistence`; true when anything opted in
1627
1627
  function configure(manifest) {
1628
1628
  state.configured = true;
1629
+ state.manifest = manifest || null;
1629
1630
  state.sources.clear();
1630
1631
  for (const [name, source] of Object.entries(manifest?.data || {})) {
1631
1632
  if (!source || typeof source !== 'object') continue;
@@ -1655,6 +1656,7 @@ window.ManifestDataStore = {
1655
1656
  if (state.enabled || state.disabled) return state.enabled;
1656
1657
  if (typeof indexedDB === 'undefined' || !indexedDB) return false;
1657
1658
  state.enabled = true;
1659
+ watchAuthSettle(state.manifest);
1658
1660
  if (!state.dbName) state.dbName = `manifest:${(typeof location !== 'undefined' && location.origin) || 'null'}`;
1659
1661
  if (!state.frameworkVersion) state.frameworkVersion = buildVersion;
1660
1662
  state.scope = evaluateScope();
@@ -1797,7 +1799,7 @@ window.ManifestDataStore = {
1797
1799
  if (wasPending) onLanded([...state.sources.keys()]); // rows that landed while pending get written under the real scope
1798
1800
  }
1799
1801
  if (typeof window !== 'undefined') {
1800
- try { window.dispatchEvent(new CustomEvent('manifest:persist:scope', { detail: { scope: next, previous: prev } })); } catch { /* no-op */ }
1802
+ try { window.dispatchEvent(new CustomEvent('manifest:persist:scope', { detail: { scope: next, previous: prev, boot: wasPending } })); } catch { /* no-op */ }
1801
1803
  }
1802
1804
  }
1803
1805
 
@@ -32,7 +32,7 @@ const URGENT_WINDOW_MS = 1500;
32
32
  const URGENT_CAP = 8;
33
33
  const PREWARM_CAP = (window.ManifestDeferConfig && window.ManifestDeferConfig.prewarmCap) || 48; // warm-but-unopened containers kept live
34
34
  const IDLE_BUDGET_MS = 4;
35
- const SLICE_MAX = 8;
35
+ const SLICE_MAX = 2; // containers per idle slice: one big menu render is already a long task, so keep slices short
36
36
  const warm = new Set(); // prewarmed, never opened by the user
37
37
  let lastGestureAt = -Infinity;
38
38
  let urgentWindowAt = -Infinity;