@civitai/blocks-react 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -307,6 +307,21 @@ const { keys } = await storage.list({ prefix: 'note-' });
307
307
  const quota = await storage.getQuota(); // { usedBytes, rowCount, limitBytes, limitRows }
308
308
  ```
309
309
 
310
+ ### `useSharedStorage()`
311
+
312
+ App-scoped, append-only, community-votable SHARED datastore (every viewer sees
313
+ the same list). Sibling of `useAppStorage`; anonymous viewers get the read path
314
+ and a hard reject on mutations.
315
+
316
+ ```tsx
317
+ const shared = useSharedStorage();
318
+ const { key } = await shared.append({ title: 'Add dark mode', body: 'please' });
319
+ const { items } = await shared.list({ limit: 20 }); // newest-first
320
+ const count = await shared.vote(key); // idempotent up-vote
321
+ await shared.unvote(key);
322
+ await shared.withdraw(key); // remove my own entry
323
+ ```
324
+
310
325
  ### `useCheckpointPicker()`
311
326
 
312
327
  Drive the platform Checkpoint picker + persist a viewer override.
@@ -317,6 +332,55 @@ const { selected } = await open({ baseModelGroup: 'SDXL', currentVersionId });
317
332
  if (selected) await persist(selected.versionId); // null clears the override
318
333
  ```
319
334
 
335
+ ### `useResourcePicker()`
336
+
337
+ Drive the platform resource picker for page blocks — `'Checkpoint' | 'LORA'`.
338
+ The viewer searches in host chrome; the block only ever sees the one resource it
339
+ picked. DISCOVERY ONLY — the returned `versionId` is re-validated + re-priced
340
+ server-side at estimate/submit.
341
+
342
+ ```tsx
343
+ const { open } = useResourcePicker();
344
+ const picked = await open({ resourceType: 'LORA', baseModelGroup: 'SDXL' });
345
+ if (picked) {
346
+ const versionId = picked.versionId; // feed into body.additionalResources
347
+ const weight = picked.strength; // recommended default weight (may be undefined)
348
+ }
349
+ ```
350
+
351
+ ### `useImageUpload()`
352
+
353
+ Host-mediated image upload — the host opens its native upload modal and the
354
+ iframe never handles the bytes. Resolves with a moderated image (or `null` on
355
+ dismiss); pass `{ purpose: 'generationSource' }` for an unscanned img2img source
356
+ or `{ asyncScan: true }` for the early-resolve + `scanStatus()` flow.
357
+
358
+ ```tsx
359
+ const { open } = useImageUpload();
360
+ const img = await open(); // BlockUploadedImageInfo | null
361
+ if (img) {
362
+ await submit({
363
+ kind: 'textToImage',
364
+ modelId,
365
+ modelVersionId,
366
+ sourceImage: { url: img.url, width: 1024, height: 1024 },
367
+ params: { prompt },
368
+ });
369
+ }
370
+ ```
371
+
372
+ ### `useGenerationResources()`
373
+
374
+ Rehydrate a saved set of generation resources by version id — WITHOUT re-opening
375
+ the picker. Returns the same widened projection `useResourcePicker` yields
376
+ (recommended weights, trigger words, clipSkip). DISCOVERY ONLY.
377
+
378
+ ```tsx
379
+ const { fetch } = useGenerationResources();
380
+ const resources = await fetch([691639, 666002]); // by saved versionIds
381
+ const first = resources[0]; // .versionId / .strength / .trainedWords / .clipSkip
382
+ ```
383
+
320
384
  ### `useCivitaiNavigate()`
321
385
 
322
386
  Request a navigation within civitai.com (host-mediated; fire-and-forget).
@@ -335,6 +399,56 @@ const { track } = useBlockAnalytics();
335
399
  track('generate_clicked', { modelId });
336
400
  ```
337
401
 
402
+ ### `useRequestSignIn()`
403
+
404
+ Ask the host to open its sign-in flow for an ANONYMOUS viewer (fire-and-forget).
405
+ On sign-in the host re-inits the block with the now-authenticated viewer.
406
+
407
+ ```tsx
408
+ const { requestSignIn } = useRequestSignIn();
409
+ // e.g. onClick of a "Sign in to generate" button:
410
+ requestSignIn();
411
+ ```
412
+
413
+ ### `useRequestConsent()`
414
+
415
+ Lazy consent: ask the host to open its consent UI when a LOGGED-IN viewer takes
416
+ an action whose consent-gated scope the block token is missing (e.g. Generate
417
+ needs `ai:write:budgeted` but the viewer hasn't granted it). Fire-and-forget —
418
+ on grant the host pushes a new token; observe `useBlockToken().scopes` and retry.
419
+
420
+ ```tsx
421
+ const { requestConsent } = useRequestConsent();
422
+ requestConsent({ scopes: ['ai:write:budgeted', 'buzz:read:self'] });
423
+ ```
424
+
425
+ ### `useDomainMaturity()`
426
+
427
+ Read the surrounding color-domain's maturity ceiling (civitai #2670) so a block
428
+ can hide/blur mature affordances on a SFW domain. **Fail-closed SFW** until
429
+ `BLOCK_INIT` lands or against a host that predates the field.
430
+
431
+ ```tsx
432
+ const { isSfw, isLevelAllowed } = useDomainMaturity();
433
+ const showRSlider = isLevelAllowed(BrowsingLevel.R); // false on a SFW domain
434
+ ```
435
+
436
+ ### `SfwGate`
437
+
438
+ Convenience component that renders `children` only when the domain permits the
439
+ maturity — no `level` prop gates on the SFW ceiling, a `level` prop gates on that
440
+ browsing-level bit. Fail-closed SFW.
441
+
442
+ ```tsx
443
+ function MatureSection() {
444
+ return (
445
+ <SfwGate level={BrowsingLevel.R} fallback={<SafePlaceholder />}>
446
+ <RRatedControl />
447
+ </SfwGate>
448
+ );
449
+ }
450
+ ```
451
+
338
452
  ## The `/ui` subexport
339
453
 
340
454
  Opinionated components, imported separately so a transport-only block stays lean.
@@ -469,6 +583,9 @@ Runnable, minimal blocks — one per feature, each with its own README:
469
583
 
470
584
  | `@civitai/blocks-react` | pairs with `@civitai/app-sdk` | adds |
471
585
  |---|---|---|
586
+ | `0.27.x` | `^0.23.0` | async-scan image upload; transport validators for all `SHARED_*` / `APP_STORAGE_*` / picker replies |
587
+ | `0.26.x` | `^0.22.0` | `useViewer()` (`GET_VIEWER`) |
588
+ | `0.25.x` | `^0.21.0` | `useBuzzTransactions`, `useBuzzAccounts`, `useDailyCompensation`, `useWildcardPack` |
472
589
  | `0.5.0` | `^0.7.0` | `useBuzzWorkflow().cancel()` (real server-side cancel, gotcha #51) |
473
590
  | `0.4.x` | `^0.6.0` | `useAppStorage`, `SettingsForm` (`/ui`) |
474
591
  | `0.3.x` | `^0.5.0` | earlier hook set |
@@ -20,4 +20,10 @@ import type { BlockToken } from '@civitai/app-sdk/blocks';
20
20
  export declare function useBlockToken(): BlockToken & {
21
21
  refresh: () => Promise<void>;
22
22
  };
23
+ /**
24
+ * Mint/await a token refresh for one block instance, coalescing concurrent calls
25
+ * for the SAME `blockInstanceId`. Exported for the inline-mode multi-instance
26
+ * dedup test — hooks call it via `useBlockToken().refresh`.
27
+ */
28
+ export declare function requestRefresh(blockInstanceId: string): Promise<void>;
23
29
  //# sourceMappingURL=useBlockToken.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useBlockToken.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockToken.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAS1D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG;IAAE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAwB7E"}
1
+ {"version":3,"file":"useBlockToken.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockToken.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAS1D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG;IAAE,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAwB7E;AAiBD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3E"}
@@ -47,22 +47,35 @@ export function useBlockToken() {
47
47
  return { ...token, refresh: () => requestRefresh(instanceId) };
48
48
  }
49
49
  /**
50
- * In-flight dedup. Both the scheduled-timeout path and the "already past"
51
- * synchronous path can fire while a previous refresh is still pending —
52
- * coalescing into a single round-trip avoids fanning out duplicate
53
- * REQUEST_TOKEN messages, which would otherwise hit the host once per
50
+ * In-flight dedup, keyed by `blockInstanceId`. Both the scheduled-timeout path
51
+ * and the "already past" synchronous path can fire while a previous refresh is
52
+ * still pending — coalescing into a single round-trip avoids fanning out
53
+ * duplicate REQUEST_TOKEN messages, which would otherwise hit the host once per
54
54
  * snapshot update.
55
+ *
56
+ * The key MUST include `blockInstanceId`: in iframe mode there is one module per
57
+ * iframe so any key works, but on the INLINE transport (v2) several block
58
+ * instances share one module context. A module-global singleton would coalesce
59
+ * across instances, so instance B's 401-retry `refresh()` would resolve on
60
+ * instance A's in-flight `REQUEST_TOKEN` and B would never mint its OWN token.
61
+ */
62
+ const inFlightRefreshByInstance = new Map();
63
+ /**
64
+ * Mint/await a token refresh for one block instance, coalescing concurrent calls
65
+ * for the SAME `blockInstanceId`. Exported for the inline-mode multi-instance
66
+ * dedup test — hooks call it via `useBlockToken().refresh`.
55
67
  */
56
- let inFlightRefresh = null;
57
- async function requestRefresh(blockInstanceId) {
58
- if (inFlightRefresh)
59
- return inFlightRefresh;
68
+ export async function requestRefresh(blockInstanceId) {
69
+ const existing = inFlightRefreshByInstance.get(blockInstanceId);
70
+ if (existing)
71
+ return existing;
60
72
  const transport = getTransport();
61
- inFlightRefresh = sendTypedRequest(transport, { type: 'REQUEST_TOKEN', payload: { blockInstanceId } }, 'TOKEN_REFRESH_RESPONSE')
73
+ const refresh = sendTypedRequest(transport, { type: 'REQUEST_TOKEN', payload: { blockInstanceId } }, 'TOKEN_REFRESH_RESPONSE')
62
74
  .then(() => undefined)
63
75
  .finally(() => {
64
- inFlightRefresh = null;
76
+ inFlightRefreshByInstance.delete(blockInstanceId);
65
77
  });
66
- return inFlightRefresh;
78
+ inFlightRefreshByInstance.set(blockInstanceId, refresh);
79
+ return refresh;
67
80
  }
68
81
  //# sourceMappingURL=useBlockToken.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useBlockToken.js","sourceRoot":"","sources":["../../src/hooks/useBlockToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,sEAAsE;AACtE,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEtC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;IACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC;IAExC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO;QACxB,MAAM,cAAc,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC;QAChF,IAAI,cAAc,IAAI,CAAC,EAAE,CAAC;YACxB,iDAAiD;YACjD,oEAAoE;YACpE,6CAA6C;YAC7C,cAAc,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE;YACzB,oEAAoE;YACpE,6CAA6C;YAC7C,cAAc,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC7C,CAAC,EAAE,cAAc,CAAC,CAAC;QACnB,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAE9C,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;AACjE,CAAC;AAED;;;;;;GAMG;AACH,IAAI,eAAe,GAAyB,IAAI,CAAC;AAEjD,KAAK,UAAU,cAAc,CAAC,eAAuB;IACnD,IAAI,eAAe;QAAE,OAAO,eAAe,CAAC;IAC5C,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,eAAe,GAAG,gBAAgB,CAChC,SAAS,EACT,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,EAAE,EACvD,wBAAwB,CACzB;SACE,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;SACrB,OAAO,CAAC,GAAG,EAAE;QACZ,eAAe,GAAG,IAAI,CAAC;IACzB,CAAC,CAAC,CAAC;IACL,OAAO,eAAe,CAAC;AACzB,CAAC"}
1
+ {"version":3,"file":"useBlockToken.js","sourceRoot":"","sources":["../../src/hooks/useBlockToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,sEAAsE;AACtE,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEtC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;IACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC;IAExC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO;QACxB,MAAM,cAAc,GAAG,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC;QAChF,IAAI,cAAc,IAAI,CAAC,EAAE,CAAC;YACxB,iDAAiD;YACjD,oEAAoE;YACpE,6CAA6C;YAC7C,cAAc,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE;YACzB,oEAAoE;YACpE,6CAA6C;YAC7C,cAAc,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC7C,CAAC,EAAE,cAAc,CAAC,CAAC;QACnB,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAE9C,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,yBAAyB,GAAG,IAAI,GAAG,EAAyB,CAAC;AAEnE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,eAAuB;IAC1D,MAAM,QAAQ,GAAG,yBAAyB,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAChE,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,gBAAgB,CAC9B,SAAS,EACT,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,EAAE,EACvD,wBAAwB,CACzB;SACE,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;SACrB,OAAO,CAAC,GAAG,EAAE;QACZ,yBAAyB,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IACL,yBAAyB,CAAC,GAAG,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IACxD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useGenerationResources.d.ts","sourceRoot":"","sources":["../../src/hooks/useGenerationResources.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AASjE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;CAC/D,CAyBA"}
1
+ {"version":3,"file":"useGenerationResources.d.ts","sourceRoot":"","sources":["../../src/hooks/useGenerationResources.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAiBjE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,IAAI;IACxC,KAAK,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;CAC/D,CA0DA"}
@@ -1,7 +1,14 @@
1
- import { useCallback } from 'react';
1
+ import { useCallback, useEffect, useRef } from 'react';
2
2
  import { buildGenerationResourcesUrl, responseToResources, } from '../api/generationResources.js';
3
3
  import { useHostOrigin } from './useHostOrigin.js';
4
4
  import { useBlockToken } from './useBlockToken.js';
5
+ /**
6
+ * Backstop timeout for the direct REST fetch. Unlike the postMessage hooks
7
+ * (which inherit the transport's 30s request timeout), this hook talks to the
8
+ * HTTP API directly, so it needs its OWN bound — otherwise a hung request never
9
+ * rejects. Matches the transport's default request timeout.
10
+ */
11
+ const GENERATION_RESOURCES_TIMEOUT_MS = 30_000;
5
12
  /**
6
13
  * Rehydrate a saved set of generation resources by version id, WITHOUT
7
14
  * re-opening the resource picker. `fetch(versionIds)` GETs the block-token-gated
@@ -27,19 +34,49 @@ import { useBlockToken } from './useBlockToken.js';
27
34
  export function useGenerationResources() {
28
35
  const host = useHostOrigin();
29
36
  const { raw } = useBlockToken();
37
+ // In-flight AbortControllers, aborted on unmount so a pending fetch never
38
+ // resolves into an unmounted component (and its timer is cleared).
39
+ const inFlight = useRef(new Set());
40
+ useEffect(() => {
41
+ const controllers = inFlight.current;
42
+ return () => {
43
+ for (const c of controllers)
44
+ c.abort();
45
+ controllers.clear();
46
+ };
47
+ }, []);
30
48
  const doFetch = useCallback(async (versionIds) => {
31
49
  if (!host) {
32
50
  throw new Error('useGenerationResources: host origin not established yet (wait for BLOCK_INIT).');
33
51
  }
34
52
  const url = host + buildGenerationResourcesUrl(versionIds);
35
- const res = await fetch(url, {
36
- headers: { Authorization: `Bearer ${raw}` },
37
- });
38
- if (!res.ok) {
39
- throw new Error(`generation-resources request failed (${res.status})`);
53
+ const controller = new AbortController();
54
+ inFlight.current.add(controller);
55
+ const timeoutId = setTimeout(() => controller.abort(), GENERATION_RESOURCES_TIMEOUT_MS);
56
+ try {
57
+ const res = await fetch(url, {
58
+ headers: { Authorization: `Bearer ${raw}` },
59
+ signal: controller.signal,
60
+ });
61
+ if (!res.ok) {
62
+ throw new Error(`generation-resources request failed (${res.status})`);
63
+ }
64
+ const body = (await res.json());
65
+ return responseToResources(body);
66
+ }
67
+ catch (err) {
68
+ // Distinguish an abort (timeout OR unmount) from a real network/HTTP
69
+ // failure so the caller gets an actionable message instead of a bare
70
+ // DOMException.
71
+ if (controller.signal.aborted) {
72
+ throw new Error(`useGenerationResources: request aborted (timed out after ${GENERATION_RESOURCES_TIMEOUT_MS}ms or the hook unmounted).`);
73
+ }
74
+ throw err;
75
+ }
76
+ finally {
77
+ clearTimeout(timeoutId);
78
+ inFlight.current.delete(controller);
40
79
  }
41
- const body = (await res.json());
42
- return responseToResources(body);
43
80
  }, [host, raw]);
44
81
  return { fetch: doFetch };
45
82
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useGenerationResources.js","sourceRoot":"","sources":["../../src/hooks/useGenerationResources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAIpC,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,sBAAsB;IAGpC,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;IAC7B,MAAM,EAAE,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;IAEhC,MAAM,OAAO,GAAG,WAAW,CACzB,KAAK,EAAE,UAAoB,EAAgC,EAAE;QAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CACb,gFAAgF,CACjF,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,2BAA2B,CAAC,UAAU,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,GAAG,EAAE,EAAE;SAC5C,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAY,CAAC;QAC3C,OAAO,mBAAmB,CAAC,IAAiD,CAAC,CAAC;IAChF,CAAC,EACD,CAAC,IAAI,EAAE,GAAG,CAAC,CACZ,CAAC;IAEF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"useGenerationResources.js","sourceRoot":"","sources":["../../src/hooks/useGenerationResources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAIvD,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,+BAA+B,GAAG,MAAM,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,sBAAsB;IAGpC,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;IAC7B,MAAM,EAAE,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;IAEhC,0EAA0E;IAC1E,mEAAmE;IACnE,MAAM,QAAQ,GAAG,MAAM,CAAuB,IAAI,GAAG,EAAE,CAAC,CAAC;IACzD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC;QACrC,OAAO,GAAG,EAAE;YACV,KAAK,MAAM,CAAC,IAAI,WAAW;gBAAE,CAAC,CAAC,KAAK,EAAE,CAAC;YACvC,WAAW,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,WAAW,CACzB,KAAK,EAAE,UAAoB,EAAgC,EAAE;QAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CACb,gFAAgF,CACjF,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,2BAA2B,CAAC,UAAU,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACjC,MAAM,SAAS,GAAG,UAAU,CAC1B,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EACxB,+BAA+B,CAChC,CAAC;QACF,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,GAAG,EAAE,EAAE;gBAC3C,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;YACzE,CAAC;YACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAY,CAAC;YAC3C,OAAO,mBAAmB,CAAC,IAAiD,CAAC,CAAC;QAChF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,qEAAqE;YACrE,qEAAqE;YACrE,gBAAgB;YAChB,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CACb,4DAA4D,+BAA+B,4BAA4B,CACxH,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,EACD,CAAC,IAAI,EAAE,GAAG,CAAC,CACZ,CAAC;IAEF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"liveHost.d.ts","sourceRoot":"","sources":["../../src/internal/liveHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,WAAW,EAChB,KAAK,KAAK,EACV,KAAK,UAAU,EAGhB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,QAAQ,EAAyC,MAAM,eAAe,CAAC;AACrF,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAK5B;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,wEAAwE;IACxE,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,UAAU,CAAC;IACpC;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACvD;AAQD;;;;;GAKG;AACH,UAAU,wBAAwB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,wBAAwB,CAgB/E;AAyHD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CAg9BjE"}
1
+ {"version":3,"file":"liveHost.d.ts","sourceRoot":"","sources":["../../src/internal/liveHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,WAAW,EAChB,KAAK,KAAK,EACV,KAAK,UAAU,EAGhB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,QAAQ,EAAyC,MAAM,eAAe,CAAC;AACrF,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAK5B;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,wEAAwE;IACxE,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,UAAU,CAAC;IACpC;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACvD;AAQD;;;;;GAKG;AACH,UAAU,wBAAwB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,wBAAwB,CAgB/E;AAyHD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CAssCjE"}
@@ -727,6 +727,23 @@ export function createLiveHost(options) {
727
727
  }, 'RESOURCE_PICKER_RESULT', requestId ?? '');
728
728
  return;
729
729
  }
730
+ case 'OPEN_IMAGE_UPLOAD': {
731
+ // No headless upload contract: the real image-upload flow needs the
732
+ // host's native modal + the session-authed byte pipeline, neither of
733
+ // which the dev harness has. Rather than let `useImageUpload()` hang
734
+ // to its 10-minute picker timeout, reply DISMISSED (no `selected`, so
735
+ // the hook resolves to `null`) and log an actionable message. Honest-
736
+ // by-design — never fabricate a moderated image id. Mirrors the
737
+ // OPEN_BUZZ_PURCHASE "no headless contract" pattern.
738
+ logOnce('open-image-upload', 'OPEN_IMAGE_UPLOAD received but live dev mode cannot serve the native upload ' +
739
+ 'modal — resolving the upload as dismissed (null). Test the image-upload flow ' +
740
+ 'against the real site, or use createMockHost({ cannedImageUpload }) in dev:mock.');
741
+ dispatchToBlock({
742
+ type: 'IMAGE_UPLOAD_RESULT',
743
+ payload: { requestId: requestId ?? '' },
744
+ });
745
+ return;
746
+ }
730
747
  case 'SET_USER_CHECKPOINT': {
731
748
  // FORWARD (faithful) to blocks.updateUserSettings — never fabricate.
732
749
  // Mirror the prod IframeHost's versionId validation: a number or an
@@ -915,6 +932,192 @@ export function createLiveHost(options) {
915
932
  });
916
933
  return;
917
934
  }
935
+ // ---- SHARED (cross-user, votable) storage ----
936
+ // FORWARD the eight `SHARED_*` bridges to the block-token
937
+ // `apps.shared.{list,getCount,getCounts,append,update,vote,unvote,
938
+ // withdraw}` procedures (reads GET, mutations POST — FLAT
939
+ // `{ blockToken, … }` input, same convention as apps.storage.*). Reads
940
+ // need the `apps:storage:shared:read` scope, writes
941
+ // `apps:storage:shared:write`; the dev token carries whatever the local
942
+ // manifest declared and the server enforces. Without these,
943
+ // `useSharedStorage()` hung to its 30s timeout in dev:live.
944
+ case 'SHARED_LIST': {
945
+ const rawLimit = typed.payload?.limit;
946
+ const limit = typeof rawLimit === 'number'
947
+ ? Math.min(Math.max(Math.floor(rawLimit), 1), 100)
948
+ : 50;
949
+ const listInput = { blockToken: rawToken, limit };
950
+ if (typeof typed.payload?.prefix === 'string')
951
+ listInput.prefix = typed.payload.prefix;
952
+ if (typeof typed.payload?.cursor === 'string')
953
+ listInput.cursor = typed.payload.cursor;
954
+ void callTrpcData('apps.shared.list', listInput, 'GET').then((r) => {
955
+ if (r.error) {
956
+ dispatchToBlock({
957
+ type: 'SHARED_LIST_RESULT',
958
+ payload: { requestId: requestId ?? '', items: [], error: r.error },
959
+ });
960
+ return;
961
+ }
962
+ const rawItems = r.data?.items;
963
+ const items = (Array.isArray(rawItems) ? rawItems : []).map((it) => {
964
+ const e = it;
965
+ const iso = (d) => d instanceof Date ? d.toISOString() : String(d);
966
+ return {
967
+ key: String(e.key),
968
+ authorUserId: typeof e.authorUserId === 'number' ? e.authorUserId : 0,
969
+ value: e.value,
970
+ count: typeof e.count === 'number' ? e.count : 0,
971
+ createdAt: iso(e.createdAt),
972
+ updatedAt: iso(e.updatedAt),
973
+ };
974
+ });
975
+ const nextCursor = r.data?.nextCursor;
976
+ dispatchToBlock({
977
+ type: 'SHARED_LIST_RESULT',
978
+ payload: {
979
+ requestId: requestId ?? '',
980
+ items,
981
+ ...(typeof nextCursor === 'string' && nextCursor.length > 0
982
+ ? { nextCursor }
983
+ : {}),
984
+ },
985
+ });
986
+ });
987
+ return;
988
+ }
989
+ case 'SHARED_GET_COUNT': {
990
+ const key = typed.payload?.key ?? '';
991
+ void callTrpcData('apps.shared.getCount', { blockToken: rawToken, key }, 'GET').then((r) => {
992
+ if (r.error) {
993
+ dispatchToBlock({
994
+ type: 'SHARED_GET_COUNT_RESULT',
995
+ payload: { requestId: requestId ?? '', count: 0, error: r.error },
996
+ });
997
+ return;
998
+ }
999
+ const count = r.data?.count;
1000
+ dispatchToBlock({
1001
+ type: 'SHARED_GET_COUNT_RESULT',
1002
+ payload: { requestId: requestId ?? '', count: typeof count === 'number' ? count : 0 },
1003
+ });
1004
+ });
1005
+ return;
1006
+ }
1007
+ case 'SHARED_GET_COUNTS': {
1008
+ const keys = Array.isArray(typed.payload?.keys) ? typed.payload.keys : [];
1009
+ void callTrpcData('apps.shared.getCounts', { blockToken: rawToken, keys }, 'GET').then((r) => {
1010
+ if (r.error) {
1011
+ dispatchToBlock({
1012
+ type: 'SHARED_GET_COUNTS_RESULT',
1013
+ payload: { requestId: requestId ?? '', counts: {}, error: r.error },
1014
+ });
1015
+ return;
1016
+ }
1017
+ const counts = r.data?.counts;
1018
+ dispatchToBlock({
1019
+ type: 'SHARED_GET_COUNTS_RESULT',
1020
+ payload: {
1021
+ requestId: requestId ?? '',
1022
+ counts: (counts && typeof counts === 'object' ? counts : {}),
1023
+ },
1024
+ });
1025
+ });
1026
+ return;
1027
+ }
1028
+ case 'SHARED_APPEND': {
1029
+ const value = typed.payload?.value;
1030
+ void callTrpcData('apps.shared.append', { blockToken: rawToken, value }, 'POST').then((r) => {
1031
+ if (r.error) {
1032
+ dispatchToBlock({
1033
+ type: 'SHARED_APPEND_RESULT',
1034
+ payload: { requestId: requestId ?? '', key: '', error: r.error },
1035
+ });
1036
+ return;
1037
+ }
1038
+ const key = r.data?.key;
1039
+ dispatchToBlock({
1040
+ type: 'SHARED_APPEND_RESULT',
1041
+ payload: { requestId: requestId ?? '', key: String(key ?? '') },
1042
+ });
1043
+ });
1044
+ return;
1045
+ }
1046
+ case 'SHARED_UPDATE': {
1047
+ const key = typed.payload?.key ?? '';
1048
+ const value = typed.payload?.value;
1049
+ void callTrpcData('apps.shared.update', { blockToken: rawToken, key, value }, 'POST').then((r) => {
1050
+ dispatchToBlock({
1051
+ type: 'SHARED_UPDATE_RESULT',
1052
+ payload: r.error
1053
+ ? { requestId: requestId ?? '', ok: false, error: r.error }
1054
+ : { requestId: requestId ?? '', ok: true },
1055
+ });
1056
+ });
1057
+ return;
1058
+ }
1059
+ case 'SHARED_VOTE': {
1060
+ const key = typed.payload?.key ?? '';
1061
+ void callTrpcData('apps.shared.vote', { blockToken: rawToken, key }, 'POST').then((r) => {
1062
+ if (r.error) {
1063
+ dispatchToBlock({
1064
+ type: 'SHARED_VOTE_RESULT',
1065
+ payload: { requestId: requestId ?? '', count: 0, error: r.error },
1066
+ });
1067
+ return;
1068
+ }
1069
+ const count = r.data?.count;
1070
+ dispatchToBlock({
1071
+ type: 'SHARED_VOTE_RESULT',
1072
+ payload: { requestId: requestId ?? '', count: typeof count === 'number' ? count : 0 },
1073
+ });
1074
+ });
1075
+ return;
1076
+ }
1077
+ case 'SHARED_UNVOTE': {
1078
+ const key = typed.payload?.key ?? '';
1079
+ void callTrpcData('apps.shared.unvote', { blockToken: rawToken, key }, 'POST').then((r) => {
1080
+ if (r.error) {
1081
+ dispatchToBlock({
1082
+ type: 'SHARED_UNVOTE_RESULT',
1083
+ payload: { requestId: requestId ?? '', count: 0, error: r.error },
1084
+ });
1085
+ return;
1086
+ }
1087
+ const count = r.data?.count;
1088
+ dispatchToBlock({
1089
+ type: 'SHARED_UNVOTE_RESULT',
1090
+ payload: { requestId: requestId ?? '', count: typeof count === 'number' ? count : 0 },
1091
+ });
1092
+ });
1093
+ return;
1094
+ }
1095
+ case 'SHARED_WITHDRAW': {
1096
+ const key = typed.payload?.key ?? '';
1097
+ void callTrpcData('apps.shared.withdraw', { blockToken: rawToken, key }, 'POST').then((r) => {
1098
+ if (r.error) {
1099
+ dispatchToBlock({
1100
+ type: 'SHARED_WITHDRAW_RESULT',
1101
+ payload: {
1102
+ requestId: requestId ?? '',
1103
+ ok: false,
1104
+ deleted: false,
1105
+ error: r.error,
1106
+ },
1107
+ });
1108
+ return;
1109
+ }
1110
+ dispatchToBlock({
1111
+ type: 'SHARED_WITHDRAW_RESULT',
1112
+ payload: {
1113
+ requestId: requestId ?? '',
1114
+ ok: true,
1115
+ deleted: Boolean(r.data?.deleted),
1116
+ },
1117
+ });
1118
+ });
1119
+ return;
1120
+ }
918
1121
  case 'NAVIGATE': {
919
1122
  const path = typed.payload?.path ?? '';
920
1123
  const target = typed.payload?.target ?? 'current';