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.
- package/lib/manifest.appwrite.data.js +1 -1
- package/lib/manifest.chat.js +29 -2
- package/lib/manifest.d.ts +32 -22
- package/lib/manifest.data.js +4 -2
- package/lib/manifest.defer.js +1 -1
- package/lib/manifest.device.js +491 -0
- package/lib/manifest.edit.js +32 -4
- package/lib/manifest.integrity.json +12 -11
- package/lib/manifest.js +28 -12
- package/lib/manifest.localization.js +11 -7
- package/lib/manifest.native.js +1 -1
- package/lib/manifest.schema.json +6 -1
- package/lib/manifest.sw.js +1 -1
- package/lib/manifest.text.edit.js +46 -5
- package/lib/manifest.utilities.js +201 -5
- package/lib/manifest.utilities.node.mjs +2869 -0
- package/package.json +1 -1
package/lib/manifest.chat.js
CHANGED
|
@@ -121,10 +121,20 @@
|
|
|
121
121
|
state.resetQueued = true;
|
|
122
122
|
queueMicrotask(() => { state.resetQueued = false; reset(); });
|
|
123
123
|
};
|
|
124
|
-
window
|
|
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
|
|
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
|
|
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
|
|
285
|
+
/** `$push` from the device plugin — push/notification permissions and token. */
|
|
286
286
|
export interface ManifestPush {
|
|
287
|
-
|
|
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
|
|
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
|
|
309
|
+
/** `$links` from the device plugin — deep/universal link handling. */
|
|
303
310
|
export interface ManifestLinks {
|
|
304
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
478
|
+
/** Device/platform info (utilities; enriched by the device plugin). */
|
|
469
479
|
const $device: ManifestDevice;
|
|
470
|
-
/** Native share (
|
|
480
|
+
/** Native share (device plugin). */
|
|
471
481
|
const $share: ManifestShare;
|
|
472
|
-
/** Push permissions (
|
|
482
|
+
/** Push permissions (device plugin). */
|
|
473
483
|
const $push: ManifestPush;
|
|
474
|
-
/** Secure storage (
|
|
484
|
+
/** Secure storage (device plugin). */
|
|
475
485
|
const $secure: ManifestSecure;
|
|
476
|
-
/** Deep links (
|
|
486
|
+
/** Deep links (device plugin). */
|
|
477
487
|
const $links: ManifestLinks;
|
|
478
|
-
/** App foreground state (
|
|
488
|
+
/** App foreground state (device plugin). */
|
|
479
489
|
const $app: ManifestApp;
|
|
480
|
-
/** Haptics (
|
|
490
|
+
/** Haptics (device plugin). */
|
|
481
491
|
const $haptics: ManifestHaptics;
|
|
482
|
-
/** Biometric auth (
|
|
492
|
+
/** Biometric auth (device plugin). */
|
|
483
493
|
const $biometric: ManifestBiometric;
|
|
484
|
-
/** Camera (
|
|
494
|
+
/** Camera (device plugin). */
|
|
485
495
|
const $camera: ManifestCamera;
|
|
486
496
|
|
|
487
497
|
/** Window-level Manifest namespace exposed by the loader. */
|
package/lib/manifest.data.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
|
|
5
|
-
const MANIFEST_BUILD_VERSION = '0.5.199-next.
|
|
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
|
|
package/lib/manifest.defer.js
CHANGED
|
@@ -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 =
|
|
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;
|