@civitai/blocks-react 0.56.0 → 0.57.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.
Files changed (262) hide show
  1. package/README.md +34 -2
  2. package/dist/api/generationResources.d.ts +4 -3
  3. package/dist/hooks/returnTypeLedger.d.ts +2 -0
  4. package/dist/hooks/returnTypeLedger.js +2 -0
  5. package/dist/hooks/useAppStorage.js +2 -2
  6. package/dist/hooks/useAppWorkflows.d.ts +5 -2
  7. package/dist/hooks/useAppWorkflows.js +18 -7
  8. package/dist/hooks/useBlockAnalytics.d.ts +5 -3
  9. package/dist/hooks/useBlockAnalytics.js +1 -1
  10. package/dist/hooks/useBlockBreakpoint.d.ts +7 -1
  11. package/dist/hooks/useBlockContext.d.ts +13 -2
  12. package/dist/hooks/useBlockContext.js +1 -1
  13. package/dist/hooks/useBlockResize.d.ts +7 -1
  14. package/dist/hooks/useBlockResize.js +1 -1
  15. package/dist/hooks/useBlockSettings.d.ts +6 -1
  16. package/dist/hooks/useBlockTheme.d.ts +6 -1
  17. package/dist/hooks/useBlockToken.d.ts +8 -3
  18. package/dist/hooks/useBlockToken.js +2 -2
  19. package/dist/hooks/useBuzzAccounts.d.ts +3 -2
  20. package/dist/hooks/useBuzzAccounts.js +16 -15
  21. package/dist/hooks/useBuzzBalance.d.ts +3 -2
  22. package/dist/hooks/useBuzzBalance.js +16 -18
  23. package/dist/hooks/useBuzzPurchase.d.ts +8 -6
  24. package/dist/hooks/useBuzzPurchase.js +3 -3
  25. package/dist/hooks/useBuzzTransactions.d.ts +4 -2
  26. package/dist/hooks/useBuzzTransactions.js +16 -15
  27. package/dist/hooks/useBuzzWorkflow.d.ts +71 -28
  28. package/dist/hooks/useBuzzWorkflow.js +31 -17
  29. package/dist/hooks/useCheckpointPicker.d.ts +18 -16
  30. package/dist/hooks/useCheckpointPicker.js +3 -3
  31. package/dist/hooks/useCivitaiNavigate.d.ts +5 -3
  32. package/dist/hooks/useCivitaiNavigate.js +1 -1
  33. package/dist/hooks/useCollectionFollow.js +4 -4
  34. package/dist/hooks/useConsentUnavailable.js +2 -2
  35. package/dist/hooks/useCreatePostFromApp.js +4 -4
  36. package/dist/hooks/useDailyCompensation.d.ts +4 -2
  37. package/dist/hooks/useDailyCompensation.js +17 -15
  38. package/dist/hooks/useDirectLoad.d.ts +7 -1
  39. package/dist/hooks/useDirectLoad.js +1 -1
  40. package/dist/hooks/useDomainMaturity.d.ts +6 -2
  41. package/dist/hooks/useGatedImages.d.ts +8 -0
  42. package/dist/hooks/useGatedImages.js +10 -2
  43. package/dist/hooks/useGenerationResources.d.ts +5 -3
  44. package/dist/hooks/useHostOrigin.d.ts +7 -1
  45. package/dist/hooks/useHostOrigin.js +1 -1
  46. package/dist/hooks/useImageUpload.d.ts +44 -17
  47. package/dist/hooks/useImageUpload.js +24 -4
  48. package/dist/hooks/usePublishGenerationOutputs.js +4 -4
  49. package/dist/hooks/useRequestConsent.d.ts +7 -5
  50. package/dist/hooks/useRequestConsent.js +1 -1
  51. package/dist/hooks/useRequestSequencer.d.ts +71 -0
  52. package/dist/hooks/useRequestSequencer.js +28 -0
  53. package/dist/hooks/useRequestSignIn.d.ts +7 -5
  54. package/dist/hooks/useRequestSignIn.js +1 -1
  55. package/dist/hooks/useResourcePicker.d.ts +16 -14
  56. package/dist/hooks/useResourcePicker.js +3 -3
  57. package/dist/hooks/useSaveImage.js +2 -2
  58. package/dist/hooks/useSharedStorage.js +2 -2
  59. package/dist/hooks/useTip.js +1 -1
  60. package/dist/hooks/useTipAllowance.d.ts +13 -1
  61. package/dist/hooks/useTipAllowance.js +49 -11
  62. package/dist/hooks/useViewer.d.ts +3 -2
  63. package/dist/hooks/useViewer.js +16 -18
  64. package/dist/hooks/useWildcardPack.d.ts +3 -1
  65. package/dist/hooks/useWildcardPack.js +19 -14
  66. package/dist/index.d.ts +30 -14
  67. package/dist/index.js +7 -7
  68. package/dist/internal/consentRefusalLatch.d.ts +1 -1
  69. package/dist/internal/consentRefusalLatch.js +1 -1
  70. package/dist/internal/liveHost.d.ts +63 -11
  71. package/dist/internal/liveHost.js +240 -37
  72. package/dist/internal/mockHost.js +16 -15
  73. package/dist/internal/pickerOverlay.d.ts +52 -4
  74. package/dist/internal/pickerOverlay.js +14 -3
  75. package/dist/testing.d.ts +1 -1
  76. package/dist/testing.js +1 -1
  77. package/dist/{internal → transport}/iframeTransport.d.ts +43 -5
  78. package/dist/{internal → transport}/iframeTransport.js +142 -12
  79. package/dist/transport/originMatcher.d.ts +92 -0
  80. package/dist/transport/originMatcher.js +222 -0
  81. package/dist/transport/requestId.d.ts +113 -0
  82. package/dist/transport/requestId.js +117 -0
  83. package/dist/{internal → transport}/validate.d.ts +34 -0
  84. package/dist/{internal → transport}/validate.js +189 -48
  85. package/dist/ui/BlockGate.js +1 -1
  86. package/dist/ui/SettingsForm.d.ts +8 -2
  87. package/dist/ui/SettingsForm.js +49 -3
  88. package/package.json +7 -148
  89. package/dist/api/generationResources.d.ts.map +0 -1
  90. package/dist/api/generationResources.js.map +0 -1
  91. package/dist/hooks/SfwGate.d.ts.map +0 -1
  92. package/dist/hooks/SfwGate.js.map +0 -1
  93. package/dist/hooks/useAppStorage.d.ts.map +0 -1
  94. package/dist/hooks/useAppStorage.js.map +0 -1
  95. package/dist/hooks/useAppWorkflows.d.ts.map +0 -1
  96. package/dist/hooks/useAppWorkflows.js.map +0 -1
  97. package/dist/hooks/useBlockAnalytics.d.ts.map +0 -1
  98. package/dist/hooks/useBlockAnalytics.js.map +0 -1
  99. package/dist/hooks/useBlockBreakpoint.d.ts.map +0 -1
  100. package/dist/hooks/useBlockBreakpoint.js.map +0 -1
  101. package/dist/hooks/useBlockContext.d.ts.map +0 -1
  102. package/dist/hooks/useBlockContext.js.map +0 -1
  103. package/dist/hooks/useBlockResize.d.ts.map +0 -1
  104. package/dist/hooks/useBlockResize.js.map +0 -1
  105. package/dist/hooks/useBlockSettings.d.ts.map +0 -1
  106. package/dist/hooks/useBlockSettings.js.map +0 -1
  107. package/dist/hooks/useBlockTheme.d.ts.map +0 -1
  108. package/dist/hooks/useBlockTheme.js.map +0 -1
  109. package/dist/hooks/useBlockToken.d.ts.map +0 -1
  110. package/dist/hooks/useBlockToken.js.map +0 -1
  111. package/dist/hooks/useBuzzAccounts.d.ts.map +0 -1
  112. package/dist/hooks/useBuzzAccounts.js.map +0 -1
  113. package/dist/hooks/useBuzzBalance.d.ts.map +0 -1
  114. package/dist/hooks/useBuzzBalance.js.map +0 -1
  115. package/dist/hooks/useBuzzPurchase.d.ts.map +0 -1
  116. package/dist/hooks/useBuzzPurchase.js.map +0 -1
  117. package/dist/hooks/useBuzzTransactions.d.ts.map +0 -1
  118. package/dist/hooks/useBuzzTransactions.js.map +0 -1
  119. package/dist/hooks/useBuzzWorkflow.d.ts.map +0 -1
  120. package/dist/hooks/useBuzzWorkflow.js.map +0 -1
  121. package/dist/hooks/useCheckpointPicker.d.ts.map +0 -1
  122. package/dist/hooks/useCheckpointPicker.js.map +0 -1
  123. package/dist/hooks/useCivitaiNavigate.d.ts.map +0 -1
  124. package/dist/hooks/useCivitaiNavigate.js.map +0 -1
  125. package/dist/hooks/useCollectionFollow.d.ts.map +0 -1
  126. package/dist/hooks/useCollectionFollow.js.map +0 -1
  127. package/dist/hooks/useConsentUnavailable.d.ts.map +0 -1
  128. package/dist/hooks/useConsentUnavailable.js.map +0 -1
  129. package/dist/hooks/useCreatePostFromApp.d.ts.map +0 -1
  130. package/dist/hooks/useCreatePostFromApp.js.map +0 -1
  131. package/dist/hooks/useDailyCompensation.d.ts.map +0 -1
  132. package/dist/hooks/useDailyCompensation.js.map +0 -1
  133. package/dist/hooks/useDirectLoad.d.ts.map +0 -1
  134. package/dist/hooks/useDirectLoad.js.map +0 -1
  135. package/dist/hooks/useDomainMaturity.d.ts.map +0 -1
  136. package/dist/hooks/useDomainMaturity.js.map +0 -1
  137. package/dist/hooks/useGatedImages.d.ts.map +0 -1
  138. package/dist/hooks/useGatedImages.js.map +0 -1
  139. package/dist/hooks/useGenerationResources.d.ts.map +0 -1
  140. package/dist/hooks/useGenerationResources.js.map +0 -1
  141. package/dist/hooks/useHostOrigin.d.ts.map +0 -1
  142. package/dist/hooks/useHostOrigin.js.map +0 -1
  143. package/dist/hooks/useImageUpload.d.ts.map +0 -1
  144. package/dist/hooks/useImageUpload.js.map +0 -1
  145. package/dist/hooks/usePublishGenerationOutputs.d.ts.map +0 -1
  146. package/dist/hooks/usePublishGenerationOutputs.js.map +0 -1
  147. package/dist/hooks/useRequestConsent.d.ts.map +0 -1
  148. package/dist/hooks/useRequestConsent.js.map +0 -1
  149. package/dist/hooks/useRequestSignIn.d.ts.map +0 -1
  150. package/dist/hooks/useRequestSignIn.js.map +0 -1
  151. package/dist/hooks/useResourcePicker.d.ts.map +0 -1
  152. package/dist/hooks/useResourcePicker.js.map +0 -1
  153. package/dist/hooks/useSaveImage.d.ts.map +0 -1
  154. package/dist/hooks/useSaveImage.js.map +0 -1
  155. package/dist/hooks/useSharedStorage.d.ts.map +0 -1
  156. package/dist/hooks/useSharedStorage.js.map +0 -1
  157. package/dist/hooks/useTip.d.ts.map +0 -1
  158. package/dist/hooks/useTip.js.map +0 -1
  159. package/dist/hooks/useTipAllowance.d.ts.map +0 -1
  160. package/dist/hooks/useTipAllowance.js.map +0 -1
  161. package/dist/hooks/useViewer.d.ts.map +0 -1
  162. package/dist/hooks/useViewer.js.map +0 -1
  163. package/dist/hooks/useWildcardPack.d.ts.map +0 -1
  164. package/dist/hooks/useWildcardPack.js.map +0 -1
  165. package/dist/index.d.ts.map +0 -1
  166. package/dist/index.js.map +0 -1
  167. package/dist/internal/catalog.d.ts.map +0 -1
  168. package/dist/internal/catalog.js.map +0 -1
  169. package/dist/internal/consent.d.ts.map +0 -1
  170. package/dist/internal/consent.js.map +0 -1
  171. package/dist/internal/consentRefusalLatch.d.ts.map +0 -1
  172. package/dist/internal/consentRefusalLatch.js.map +0 -1
  173. package/dist/internal/detector.d.ts.map +0 -1
  174. package/dist/internal/detector.js.map +0 -1
  175. package/dist/internal/directLoad.d.ts.map +0 -1
  176. package/dist/internal/directLoad.js.map +0 -1
  177. package/dist/internal/iframeTransport.d.ts.map +0 -1
  178. package/dist/internal/iframeTransport.js.map +0 -1
  179. package/dist/internal/inlineTransport.d.ts.map +0 -1
  180. package/dist/internal/inlineTransport.js.map +0 -1
  181. package/dist/internal/liveHost.d.ts.map +0 -1
  182. package/dist/internal/liveHost.js.map +0 -1
  183. package/dist/internal/mockHost.d.ts.map +0 -1
  184. package/dist/internal/mockHost.js.map +0 -1
  185. package/dist/internal/originMatcher.d.ts +0 -28
  186. package/dist/internal/originMatcher.d.ts.map +0 -1
  187. package/dist/internal/originMatcher.js +0 -89
  188. package/dist/internal/originMatcher.js.map +0 -1
  189. package/dist/internal/pickerOverlay.d.ts.map +0 -1
  190. package/dist/internal/pickerOverlay.js.map +0 -1
  191. package/dist/internal/replyError.d.ts.map +0 -1
  192. package/dist/internal/replyError.js.map +0 -1
  193. package/dist/internal/requestTimeouts.d.ts.map +0 -1
  194. package/dist/internal/requestTimeouts.js.map +0 -1
  195. package/dist/internal/singleton.d.ts.map +0 -1
  196. package/dist/internal/singleton.js.map +0 -1
  197. package/dist/internal/transport.d.ts.map +0 -1
  198. package/dist/internal/transport.js.map +0 -1
  199. package/dist/internal/validate.d.ts.map +0 -1
  200. package/dist/internal/validate.js.map +0 -1
  201. package/dist/live.d.ts.map +0 -1
  202. package/dist/live.js.map +0 -1
  203. package/dist/testing.d.ts.map +0 -1
  204. package/dist/testing.js.map +0 -1
  205. package/dist/ui/Alert.d.ts.map +0 -1
  206. package/dist/ui/Alert.js.map +0 -1
  207. package/dist/ui/Badge.d.ts.map +0 -1
  208. package/dist/ui/Badge.js.map +0 -1
  209. package/dist/ui/BlockGate.d.ts.map +0 -1
  210. package/dist/ui/BlockGate.js.map +0 -1
  211. package/dist/ui/Button.d.ts.map +0 -1
  212. package/dist/ui/Button.js.map +0 -1
  213. package/dist/ui/Card.d.ts.map +0 -1
  214. package/dist/ui/Card.js.map +0 -1
  215. package/dist/ui/Collapse.d.ts.map +0 -1
  216. package/dist/ui/Collapse.js.map +0 -1
  217. package/dist/ui/FollowButton.d.ts.map +0 -1
  218. package/dist/ui/FollowButton.js.map +0 -1
  219. package/dist/ui/Group.d.ts.map +0 -1
  220. package/dist/ui/Group.js.map +0 -1
  221. package/dist/ui/Loader.d.ts.map +0 -1
  222. package/dist/ui/Loader.js.map +0 -1
  223. package/dist/ui/Modal.d.ts.map +0 -1
  224. package/dist/ui/Modal.js.map +0 -1
  225. package/dist/ui/NumberInput.d.ts.map +0 -1
  226. package/dist/ui/NumberInput.js.map +0 -1
  227. package/dist/ui/ReportButton.d.ts.map +0 -1
  228. package/dist/ui/ReportButton.js.map +0 -1
  229. package/dist/ui/ResourceCard.d.ts.map +0 -1
  230. package/dist/ui/ResourceCard.js.map +0 -1
  231. package/dist/ui/SegmentedControl.d.ts.map +0 -1
  232. package/dist/ui/SegmentedControl.js.map +0 -1
  233. package/dist/ui/Select.d.ts.map +0 -1
  234. package/dist/ui/Select.js.map +0 -1
  235. package/dist/ui/SettingsForm.d.ts.map +0 -1
  236. package/dist/ui/SettingsForm.js.map +0 -1
  237. package/dist/ui/Slider.d.ts.map +0 -1
  238. package/dist/ui/Slider.js.map +0 -1
  239. package/dist/ui/Stack.d.ts.map +0 -1
  240. package/dist/ui/Stack.js.map +0 -1
  241. package/dist/ui/TextInput.d.ts.map +0 -1
  242. package/dist/ui/TextInput.js.map +0 -1
  243. package/dist/ui/Textarea.d.ts.map +0 -1
  244. package/dist/ui/Textarea.js.map +0 -1
  245. package/dist/ui/TipButton.d.ts.map +0 -1
  246. package/dist/ui/TipButton.js.map +0 -1
  247. package/dist/ui/index.d.ts.map +0 -1
  248. package/dist/ui/index.js.map +0 -1
  249. package/dist/ui/styles.d.ts.map +0 -1
  250. package/dist/ui/styles.js.map +0 -1
  251. /package/dist/{internal → transport}/detector.d.ts +0 -0
  252. /package/dist/{internal → transport}/detector.js +0 -0
  253. /package/dist/{internal → transport}/directLoad.d.ts +0 -0
  254. /package/dist/{internal → transport}/directLoad.js +0 -0
  255. /package/dist/{internal → transport}/inlineTransport.d.ts +0 -0
  256. /package/dist/{internal → transport}/inlineTransport.js +0 -0
  257. /package/dist/{internal → transport}/requestTimeouts.d.ts +0 -0
  258. /package/dist/{internal → transport}/requestTimeouts.js +0 -0
  259. /package/dist/{internal → transport}/singleton.d.ts +0 -0
  260. /package/dist/{internal → transport}/singleton.js +0 -0
  261. /package/dist/{internal → transport}/transport.d.ts +0 -0
  262. /package/dist/{internal → transport}/transport.js +0 -0
@@ -1,6 +1,7 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
2
- import { getTransport } from '../internal/singleton.js';
3
- import { sendTypedRequest } from '../internal/transport.js';
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { getTransport } from '../transport/singleton.js';
3
+ import { sendTypedRequest } from '../transport/transport.js';
4
+ import { useRequestSequencer } from './useRequestSequencer.js';
4
5
  /** Rehydrate the host's `date`/`cursor` (a `Date` instance OR an ISO string). */
5
6
  function toDate(v) {
6
7
  return v instanceof Date ? v : new Date(v);
@@ -20,8 +21,10 @@ function toIso(v) {
20
21
  *
21
22
  * Fetches on mount and whenever `params` change (by value), and exposes `refetch`.
22
23
  * A host that never answers surfaces as an `error` after the transport's request
23
- * timeout — the hook never hangs. Late responses that arrive after unmount are
24
- * ignored. Transaction `date`s are rehydrated to `Date`; `cursor` is normalized
24
+ * timeout — the hook never hangs. Only the LATEST request may write state: page
25
+ * 1's slow reply cannot repaint (or rewind `cursor` behind) the page 2 a newer
26
+ * request already painted, and a reply that lands after unmount is dropped
27
+ * (#392). Transaction `date`s are rehydrated to `Date`; `cursor` is normalized
25
28
  * to an ISO string for round-tripping.
26
29
  *
27
30
  * @example
@@ -32,25 +35,23 @@ export function useBuzzTransactions(params) {
32
35
  const [cursor, setCursor] = useState(null);
33
36
  const [loading, setLoading] = useState(true);
34
37
  const [error, setError] = useState(null);
35
- const mountedRef = useRef(true);
36
- useEffect(() => {
37
- mountedRef.current = true;
38
- return () => {
39
- mountedRef.current = false;
40
- };
41
- }, []);
38
+ // Latest-wins + unmount guard in one predicate (#392). This hook is the one
39
+ // the issue was audit-verified against: `paramsKey` moving is exactly what
40
+ // puts two requests in flight, and a bare mount check passes for BOTH of them.
41
+ const seq = useRequestSequencer();
42
42
  // Serialize the params to a stable key so `refetch`'s identity only changes
43
43
  // when the params VALUE changes (not on every render's fresh object). The
44
44
  // callback re-parses the key so it closes over NOTHING but the key.
45
45
  const paramsKey = params ? JSON.stringify(params) : '';
46
46
  const refetch = useCallback(() => {
47
+ const token = seq.begin();
47
48
  setLoading(true);
48
49
  setError(null);
49
50
  const parsed = paramsKey ? JSON.parse(paramsKey) : undefined;
50
51
  const payload = parsed ? { params: parsed } : {};
51
52
  sendTypedRequest(getTransport(), { type: 'GET_BUZZ_TRANSACTIONS', payload }, 'BUZZ_TRANSACTIONS_RESULT')
52
53
  .then((result) => {
53
- if (!mountedRef.current)
54
+ if (!seq.isCurrent(token))
54
55
  return;
55
56
  if (result.error || !result.result) {
56
57
  // `||`, not `??`: the reply validator gates `error` on SHAPE only, so a
@@ -66,12 +67,12 @@ export function useBuzzTransactions(params) {
66
67
  setLoading(false);
67
68
  })
68
69
  .catch((err) => {
69
- if (!mountedRef.current)
70
+ if (!seq.isCurrent(token))
70
71
  return;
71
72
  setError(err instanceof Error ? err : new Error(String(err)));
72
73
  setLoading(false);
73
74
  });
74
- }, [paramsKey]);
75
+ }, [paramsKey, seq]);
75
76
  useEffect(() => {
76
77
  refetch();
77
78
  }, [refetch]);
@@ -1,6 +1,6 @@
1
1
  import type { BlockWorkflowSnapshot, WorkflowBody, WorkflowStatus } from '@civitai/app-sdk/blocks';
2
2
  /**
3
- * Default orchestrator-side hold per {@link UseBuzzWorkflowReturn.watch} poll,
3
+ * Default orchestrator-side hold per {@link UseBuzzWorkflow.watch} poll,
4
4
  * in SECONDS.
5
5
  *
6
6
  * 🔴 THE UNIT IS SECONDS, matching the orchestrator's `?wait=` parameter — not
@@ -16,9 +16,15 @@ import type { BlockWorkflowSnapshot, WorkflowBody, WorkflowStatus } from '@civit
16
16
  * its own ~12s ceiling, and the two are SEQUENTIAL — so the round trip's
17
17
  * worst case is 15 + 12 = 27s, which must stay inside the host's own
18
18
  * end-to-end response budget.
19
+ *
20
+ * 🔴 AND THE HOST ENFORCES THAT SAME 15 (#388) — `MAX_BLOCK_POLL_WAIT_SECONDS`
21
+ * in `civitai/civitai` @ `b0eb2820b5`. This default therefore sits exactly AT
22
+ * the host's cap, not under it: raising it here changes nothing on the wire,
23
+ * because the host clamps. See {@link WatchWorkflowOptions.waitSeconds} for the
24
+ * full measured contract, including the flooring.
19
25
  */
20
26
  export declare const DEFAULT_WATCH_WAIT_SECONDS = 15;
21
- /** Optional controls for {@link UseBuzzWorkflowReturn.watch}. */
27
+ /** Optional controls for {@link UseBuzzWorkflow.watch}. */
22
28
  export interface WatchWorkflowOptions {
23
29
  /**
24
30
  * Called with EVERY snapshot the host returns, intermediate ones included, in
@@ -35,18 +41,48 @@ export interface WatchWorkflowOptions {
35
41
  *
36
42
  * 🔴 This does NOT cancel the workflow — it stops watching it. Buzz is already
37
43
  * spent and the orchestrator keeps running. To actually stop the work, call
38
- * {@link UseBuzzWorkflowReturn.cancel}.
44
+ * {@link UseBuzzWorkflow.cancel}.
39
45
  */
40
46
  signal?: AbortSignal;
41
47
  /**
42
48
  * Orchestrator-side hold per poll, in **seconds**. Default
43
- * {@link DEFAULT_WATCH_WAIT_SECONDS}. `0` disables long polling and falls back
44
- * to a plain read per `intervalMs`.
49
+ * {@link DEFAULT_WATCH_WAIT_SECONDS}.
50
+ *
51
+ * 🔴 THE HOST HONOURS AND CLAMPS THIS — it is NOT advisory (#388). This
52
+ * docblock said "CURRENTLY ADVISORY … a host that does not yet read the field
53
+ * simply answers immediately, exactly as today", which was true when written
54
+ * and is not true of the deployed host. The contract, read off
55
+ * `civitai/civitai` at **`b0eb2820b5`** (5.1.120, `main`) — two files,
56
+ * because a constant that nothing calls is not a contract:
57
+ *
58
+ * - `src/server/services/blocks/workflow.service.ts:1263` declares
59
+ * `MAX_BLOCK_POLL_WAIT_SECONDS = 15`.
60
+ * - `:1286-1292` `resolveBlockPollWaitSeconds(waitSeconds?: number)` applies
61
+ * it, and `src/server/routers/blocks.router.ts:3965` is the call site.
45
62
  *
46
- * 🔴 CURRENTLY ADVISORY. It travels on the `POLL_WORKFLOW` message and a host
47
- * that does not yet read the field simply answers immediately, exactly as
48
- * today — so `watch` is correct either way, it just polls more often. See the
49
- * field's note on `BlockToParentMessage`.
63
+ * What that function does, in its own order:
64
+ * 1. a non-`number` or non-finite value → `undefined`, i.e. NO HOLD.
65
+ * 2. `Math.floor` FIRST. `0.9` is therefore not "a short hold" — it floors
66
+ * to `0` and becomes no hold at all, which the host's own comment calls
67
+ * out explicitly.
68
+ * 3. floored `<= 0` → `undefined` (no hold). So `0` — and any fraction
69
+ * below 1 — disables long polling and `watch` falls back to a plain read
70
+ * per `intervalMs`.
71
+ * 4. otherwise `Math.min(floored, 15)`. Asking for 60 gets you 15.
72
+ *
73
+ * Consequences worth planning for: only whole seconds are expressible, and no
74
+ * value above `MAX_BLOCK_POLL_WAIT_SECONDS` buys anything — `intervalMs` is
75
+ * still what bounds request rate, because a clamped hold returns sooner than
76
+ * the caller asked for.
77
+ *
78
+ * 🔴 THIS IS PROSE ABOUT ANOTHER REPO, AND NO GUARD IN THIS ONE CAN CHECK IT.
79
+ * The host checkout is not present in CI, so the honest check is a human
80
+ * reading the two files above at a named revision — which is why the sha is
81
+ * quoted rather than "as of today". A later reader comparing against a newer
82
+ * host should update the sha along with whatever moved. The `POLL_WORKFLOW`
83
+ * field's own note on `BlockToParentMessage` in `@civitai/app-sdk` covers the
84
+ * wire shape and the older-host case; this covers what the current host does
85
+ * with the value.
50
86
  */
51
87
  waitSeconds?: number;
52
88
  /**
@@ -72,7 +108,7 @@ export interface WatchWorkflowOptions {
72
108
  maxRetries?: number;
73
109
  }
74
110
  /**
75
- * Thrown by {@link UseBuzzWorkflowReturn.estimate} when the host's reply does not
111
+ * Thrown by {@link UseBuzzWorkflow.estimate} when the host's reply does not
76
112
  * carry a usable price — either because the estimate ERRORED, or because it came
77
113
  * back without a numeric `cost.total`.
78
114
  *
@@ -193,12 +229,12 @@ export declare class WorkflowEstimateError extends Error {
193
229
  constructor(snapshot: BlockWorkflowSnapshot, code: 'failed' | 'no-cost');
194
230
  }
195
231
  /**
196
- * Why {@link UseBuzzWorkflowReturn.submit} rejected. **The two differ on whether
232
+ * Why {@link UseBuzzWorkflow.submit} rejected. **The two differ on whether
197
233
  * money may already have moved** — see {@link WorkflowSubmitError.code}.
198
234
  */
199
235
  export type WorkflowSubmitErrorCode = 'exception' | 'workflow-failed';
200
236
  /**
201
- * Thrown by {@link UseBuzzWorkflowReturn.submit} when the host's reply carries no
237
+ * Thrown by {@link UseBuzzWorkflow.submit} when the host's reply carries no
202
238
  * usable workflow outcome — either the submit ERRORED before anything was queued,
203
239
  * or a workflow-shaped reply came back already failed with no price.
204
240
  *
@@ -334,7 +370,7 @@ export declare class WorkflowSubmitError extends Error {
334
370
  * `'whatif'` lands here — correctly, because the cautious money reading still
335
371
  * applies — but there is nothing to poll. Guard with
336
372
  * `err.snapshot.workflowId !== 'whatif'` before calling
337
- * {@link UseBuzzWorkflowReturn.watch} / {@link UseBuzzWorkflowReturn.poll} to
373
+ * {@link UseBuzzWorkflow.watch} / {@link UseBuzzWorkflow.poll} to
338
374
  * learn the workflow's actual fate before spending again.
339
375
  *
340
376
  * A union rather than a boolean so a future producer gets its own code without
@@ -372,7 +408,7 @@ export interface SubmitWorkflowOptions {
372
408
  */
373
409
  idempotencyKey?: string;
374
410
  }
375
- interface UseBuzzWorkflowReturn {
411
+ export interface UseBuzzWorkflow {
376
412
  /**
377
413
  * Price a workflow without queueing it. Resolves ONLY with a snapshot that
378
414
  * carries a numeric `cost.total`.
@@ -436,7 +472,7 @@ interface UseBuzzWorkflowReturn {
436
472
  submit: (body: WorkflowBody, options?: SubmitWorkflowOptions) => Promise<BlockWorkflowSnapshot>;
437
473
  /**
438
474
  * ONE host round-trip. The low-level pull primitive — you almost certainly
439
- * want {@link UseBuzzWorkflowReturn.watch} instead, which owns the loop.
475
+ * want {@link UseBuzzWorkflow.watch} instead, which owns the loop.
440
476
  */
441
477
  poll: (workflowId: string) => Promise<BlockWorkflowSnapshot>;
442
478
  /**
@@ -444,7 +480,7 @@ interface UseBuzzWorkflowReturn {
444
480
  * `onUpdate` with every intermediate snapshot along the way.
445
481
  *
446
482
  * This is the replacement for the `useEffect` + `setTimeout` backoff every
447
- * block used to hand-write around {@link UseBuzzWorkflowReturn.poll}. The app
483
+ * block used to hand-write around {@link UseBuzzWorkflow.poll}. The app
448
484
  * consumes a promise and/or a callback; the loop lives here.
449
485
  *
450
486
  * 🔴 THE LOOP IS SEQUENTIAL AND NON-OVERLAPPING BY CONSTRUCTION — each poll is
@@ -507,16 +543,23 @@ interface UseBuzzWorkflowReturn {
507
543
  * enabled.
508
544
  *
509
545
  * `estimate`/`submit` take a full {@link WorkflowBody} — the discriminated
510
- * union keyed by `kind`, with THREE members as of `@civitai/app-sdk@0.30.0`:
511
- * a `textToImage` body (`{ kind, modelId, modelVersionId, params }`), a
512
- * `customComfy` body (`kind: 'customComfy'`), or a `step` body
513
- * (`{ kind: 'step', step, params }` — a server-registered orchestrator step
514
- * such as `'chat-completion'`), never a bare `{ prompt }`. The hook forwards
515
- * the body to the host verbatim and never reads variant-specific fields, so
516
- * every member flows through unchanged, including any member added later.
546
+ * union keyed by `kind`, never a bare `{ prompt }`. The hook forwards the body
547
+ * to the host verbatim and never reads variant-specific fields, so every member
548
+ * flows through unchanged, including any member added later.
549
+ *
550
+ * 🔴 THIS COMMENT DELIBERATELY DOES NOT SAY HOW MANY MEMBERS THERE ARE, OR NAME
551
+ * THEM (#381). It used to open "with THREE members as of
552
+ * `@civitai/app-sdk@0.30.0`" and close by certifying the list "otherwise
553
+ * unchanged" — while the union had FOUR, `WorkflowBodyPassThroughStep` having
554
+ * arrived in 583e8ba (#310). The sentence whose only job was to vouch for the
555
+ * list was the sentence that went stale. `{@link WorkflowBody}`'s own docblock
556
+ * is the single description of the member set; the machine-checked copy is
557
+ * {@link WorkflowBodyArms} below, which fails `tsc` when the union changes in
558
+ * either direction. A count re-typed here could only ever repeat the defect.
517
559
  *
518
560
  * 🔴 `customComfy` IS ITSELF A UNION, on `mode` — an app CAN ship its own
519
- * ComfyUI graph. `WorkflowBodyCustomComfyRecipe` (`mode` omitted or `'recipe'`)
561
+ * ComfyUI graph, which is a CAPABILITY claim, not a member count, and so is
562
+ * stated here. `WorkflowBodyCustomComfyRecipe` (`mode` omitted or `'recipe'`)
520
563
  * names a server-registered recipe; `WorkflowBodyCustomComfyInline`
521
564
  * (`mode: 'inline'`) carries the graph itself, plus its declared AIR
522
565
  * `resources` and a `maxBuzz` bound. The inline arm is LIVE in production
@@ -524,8 +567,9 @@ interface UseBuzzWorkflowReturn {
524
567
  * recipe-only `{ kind, recipe, params }` shape — written when that was true and
525
568
  * never revisited once the arm shipped. A developer working against the live
526
569
  * feature read the equivalent claim on the type, believed it over their own
527
- * instinct, and concluded the capability did not exist. `@civitai/app-sdk`
528
- * 0.30.0 predates the inline arm; the union above is otherwise unchanged.
570
+ * instinct, and concluded the capability did not exist. That is the SAME defect
571
+ * the paragraph above records, one member over: an incomplete description of a
572
+ * union, trusted because it read as authoritative.
529
573
  *
530
574
  * @returns `{ estimate, submit, poll, watch, cancel, status, result, error }`.
531
575
  *
@@ -590,6 +634,5 @@ interface UseBuzzWorkflowReturn {
590
634
  * }
591
635
  * }
592
636
  */
593
- export declare function useBuzzWorkflow(): UseBuzzWorkflowReturn;
594
- export {};
637
+ export declare function useBuzzWorkflow(): UseBuzzWorkflow;
595
638
  //# sourceMappingURL=useBuzzWorkflow.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useState } from 'react';
2
- import { getTransport } from '../internal/singleton.js';
3
- import { generateIdempotencyKey, sendTypedRequest } from '../internal/transport.js';
2
+ import { getTransport } from '../transport/singleton.js';
3
+ import { generateIdempotencyKey, sendTypedRequest } from '../transport/transport.js';
4
4
  /**
5
5
  * Snapshot statuses that mean "no further polling is needed."
6
6
  * Used by both `submit` (a host can return an instant-fail / cached result)
@@ -25,7 +25,7 @@ const TERMINAL_STATUSES = new Set([
25
25
  */
26
26
  const WORKFLOW_REQUEST_TIMEOUT_MS = 120_000;
27
27
  /**
28
- * Default orchestrator-side hold per {@link UseBuzzWorkflowReturn.watch} poll,
28
+ * Default orchestrator-side hold per {@link UseBuzzWorkflow.watch} poll,
29
29
  * in SECONDS.
30
30
  *
31
31
  * 🔴 THE UNIT IS SECONDS, matching the orchestrator's `?wait=` parameter — not
@@ -41,6 +41,12 @@ const WORKFLOW_REQUEST_TIMEOUT_MS = 120_000;
41
41
  * its own ~12s ceiling, and the two are SEQUENTIAL — so the round trip's
42
42
  * worst case is 15 + 12 = 27s, which must stay inside the host's own
43
43
  * end-to-end response budget.
44
+ *
45
+ * 🔴 AND THE HOST ENFORCES THAT SAME 15 (#388) — `MAX_BLOCK_POLL_WAIT_SECONDS`
46
+ * in `civitai/civitai` @ `b0eb2820b5`. This default therefore sits exactly AT
47
+ * the host's cap, not under it: raising it here changes nothing on the wire,
48
+ * because the host clamps. See {@link WatchWorkflowOptions.waitSeconds} for the
49
+ * full measured contract, including the flooring.
44
50
  */
45
51
  export const DEFAULT_WATCH_WAIT_SECONDS = 15;
46
52
  /** Gap between `watch` polls, in ms. See {@link WatchWorkflowOptions.intervalMs}. */
@@ -73,7 +79,7 @@ function sleep(ms, signal) {
73
79
  });
74
80
  }
75
81
  /**
76
- * Thrown by {@link UseBuzzWorkflowReturn.estimate} when the host's reply does not
82
+ * Thrown by {@link UseBuzzWorkflow.estimate} when the host's reply does not
77
83
  * carry a usable price — either because the estimate ERRORED, or because it came
78
84
  * back without a numeric `cost.total`.
79
85
  *
@@ -244,7 +250,7 @@ export class WorkflowEstimateError extends Error {
244
250
  */
245
251
  const HOST_SYNTHESISED_WORKFLOW_ID = 'failed';
246
252
  /**
247
- * Thrown by {@link UseBuzzWorkflowReturn.submit} when the host's reply carries no
253
+ * Thrown by {@link UseBuzzWorkflow.submit} when the host's reply carries no
248
254
  * usable workflow outcome — either the submit ERRORED before anything was queued,
249
255
  * or a workflow-shaped reply came back already failed with no price.
250
256
  *
@@ -380,7 +386,7 @@ export class WorkflowSubmitError extends Error {
380
386
  * `'whatif'` lands here — correctly, because the cautious money reading still
381
387
  * applies — but there is nothing to poll. Guard with
382
388
  * `err.snapshot.workflowId !== 'whatif'` before calling
383
- * {@link UseBuzzWorkflowReturn.watch} / {@link UseBuzzWorkflowReturn.poll} to
389
+ * {@link UseBuzzWorkflow.watch} / {@link UseBuzzWorkflow.poll} to
384
390
  * learn the workflow's actual fate before spending again.
385
391
  *
386
392
  * A union rather than a boolean so a future producer gets its own code without
@@ -452,16 +458,23 @@ export class WorkflowSubmitError extends Error {
452
458
  * enabled.
453
459
  *
454
460
  * `estimate`/`submit` take a full {@link WorkflowBody} — the discriminated
455
- * union keyed by `kind`, with THREE members as of `@civitai/app-sdk@0.30.0`:
456
- * a `textToImage` body (`{ kind, modelId, modelVersionId, params }`), a
457
- * `customComfy` body (`kind: 'customComfy'`), or a `step` body
458
- * (`{ kind: 'step', step, params }` — a server-registered orchestrator step
459
- * such as `'chat-completion'`), never a bare `{ prompt }`. The hook forwards
460
- * the body to the host verbatim and never reads variant-specific fields, so
461
- * every member flows through unchanged, including any member added later.
461
+ * union keyed by `kind`, never a bare `{ prompt }`. The hook forwards the body
462
+ * to the host verbatim and never reads variant-specific fields, so every member
463
+ * flows through unchanged, including any member added later.
464
+ *
465
+ * 🔴 THIS COMMENT DELIBERATELY DOES NOT SAY HOW MANY MEMBERS THERE ARE, OR NAME
466
+ * THEM (#381). It used to open "with THREE members as of
467
+ * `@civitai/app-sdk@0.30.0`" and close by certifying the list "otherwise
468
+ * unchanged" — while the union had FOUR, `WorkflowBodyPassThroughStep` having
469
+ * arrived in 583e8ba (#310). The sentence whose only job was to vouch for the
470
+ * list was the sentence that went stale. `{@link WorkflowBody}`'s own docblock
471
+ * is the single description of the member set; the machine-checked copy is
472
+ * {@link WorkflowBodyArms} below, which fails `tsc` when the union changes in
473
+ * either direction. A count re-typed here could only ever repeat the defect.
462
474
  *
463
475
  * 🔴 `customComfy` IS ITSELF A UNION, on `mode` — an app CAN ship its own
464
- * ComfyUI graph. `WorkflowBodyCustomComfyRecipe` (`mode` omitted or `'recipe'`)
476
+ * ComfyUI graph, which is a CAPABILITY claim, not a member count, and so is
477
+ * stated here. `WorkflowBodyCustomComfyRecipe` (`mode` omitted or `'recipe'`)
465
478
  * names a server-registered recipe; `WorkflowBodyCustomComfyInline`
466
479
  * (`mode: 'inline'`) carries the graph itself, plus its declared AIR
467
480
  * `resources` and a `maxBuzz` bound. The inline arm is LIVE in production
@@ -469,8 +482,9 @@ export class WorkflowSubmitError extends Error {
469
482
  * recipe-only `{ kind, recipe, params }` shape — written when that was true and
470
483
  * never revisited once the arm shipped. A developer working against the live
471
484
  * feature read the equivalent claim on the type, believed it over their own
472
- * instinct, and concluded the capability did not exist. `@civitai/app-sdk`
473
- * 0.30.0 predates the inline arm; the union above is otherwise unchanged.
485
+ * instinct, and concluded the capability did not exist. That is the SAME defect
486
+ * the paragraph above records, one member over: an incomplete description of a
487
+ * union, trusted because it read as authoritative.
474
488
  *
475
489
  * @returns `{ estimate, submit, poll, watch, cancel, status, result, error }`.
476
490
  *
@@ -669,7 +683,7 @@ export function useBuzzWorkflow() {
669
683
  //
670
684
  // 🔴 NOT CLAIMED TO BE REACHABLE THROUGH `IframeTransport`, WHICH ALREADY
671
685
  // FAIL-CLOSES THIS. Its `payloadValidatorFor('WORKFLOW_STATUS')`
672
- // (internal/validate.ts) drops a reply whose `snapshot.status` is absent
686
+ // (transport/validate.ts) drops a reply whose `snapshot.status` is absent
673
687
  // or outside the known set, so the request never resolves at all and
674
688
  // times out instead. This guard covers the OTHER transports
675
689
  // `sendTypedRequest` accepts (mock/test hosts, `dev:live`), and is kept as
@@ -1,4 +1,21 @@
1
1
  import type { BlockCheckpointInfo } from '@civitai/app-sdk/blocks';
2
+ /** What {@link useCheckpointPicker} returns. */
3
+ export interface UseCheckpointPicker {
4
+ open: (opts: {
5
+ /**
6
+ * Ecosystem key (e.g. 'Flux1', 'SDXL'). Get it from
7
+ * `useBlockContext().context.checkpoint?.baseModel` — but for the
8
+ * picker filter the host will collapse to the ecosystem family, so
9
+ * any baseModel in the family works as a hint.
10
+ */
11
+ baseModelGroup: string;
12
+ /** Currently-selected versionId so the picker can pre-highlight it. */
13
+ currentVersionId?: number;
14
+ }) => Promise<{
15
+ selected?: BlockCheckpointInfo;
16
+ }>;
17
+ persist: (versionId: number | null) => Promise<void>;
18
+ }
2
19
  /**
3
20
  * Drives the platform-side Checkpoint picker and the persist-override flow.
4
21
  *
@@ -20,20 +37,5 @@ import type { BlockCheckpointInfo } from '@civitai/app-sdk/blocks';
20
37
  * const { selected } = await open({ baseModelGroup: 'SDXL', currentVersionId });
21
38
  * if (selected) await persist(selected.versionId); // null clears the override
22
39
  */
23
- export declare function useCheckpointPicker(): {
24
- open: (opts: {
25
- /**
26
- * Ecosystem key (e.g. 'Flux1', 'SDXL'). Get it from
27
- * `useBlockContext().context.checkpoint?.baseModel` — but for the
28
- * picker filter the host will collapse to the ecosystem family, so
29
- * any baseModel in the family works as a hint.
30
- */
31
- baseModelGroup: string;
32
- /** Currently-selected versionId so the picker can pre-highlight it. */
33
- currentVersionId?: number;
34
- }) => Promise<{
35
- selected?: BlockCheckpointInfo;
36
- }>;
37
- persist: (versionId: number | null) => Promise<void>;
38
- };
40
+ export declare function useCheckpointPicker(): UseCheckpointPicker;
39
41
  //# sourceMappingURL=useCheckpointPicker.d.ts.map
@@ -1,8 +1,8 @@
1
1
  import { useCallback } from 'react';
2
- import { HUMAN_INTERACTION_TIMEOUT_MS } from '../internal/requestTimeouts.js';
2
+ import { HUMAN_INTERACTION_TIMEOUT_MS } from '../transport/requestTimeouts.js';
3
3
  import { throwOnFailedReply } from '../internal/replyError.js';
4
- import { getTransport } from '../internal/singleton.js';
5
- import { sendTypedRequest } from '../internal/transport.js';
4
+ import { getTransport } from '../transport/singleton.js';
5
+ import { sendTypedRequest } from '../transport/transport.js';
6
6
  /**
7
7
  * Drives the platform-side Checkpoint picker and the persist-override flow.
8
8
  *
@@ -1,3 +1,7 @@
1
+ /** What {@link useCivitaiNavigate} returns. */
2
+ export interface UseCivitaiNavigate {
3
+ navigate: (path: string, target?: 'current' | 'new_tab') => void;
4
+ }
1
5
  /**
2
6
  * Requests a navigation within civitai.com. The host mediates — `target:
3
7
  * "current"` navigates the parent frame; `"new_tab"` opens a new tab (which
@@ -9,7 +13,5 @@
9
13
  * const { navigate } = useCivitaiNavigate();
10
14
  * navigate('/models/12345', 'new_tab'); // 'new_tab' needs allow-popups* in the manifest sandbox
11
15
  */
12
- export declare function useCivitaiNavigate(): {
13
- navigate: (path: string, target?: 'current' | 'new_tab') => void;
14
- };
16
+ export declare function useCivitaiNavigate(): UseCivitaiNavigate;
15
17
  //# sourceMappingURL=useCivitaiNavigate.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { useCallback } from 'react';
2
- import { getTransport } from '../internal/singleton.js';
2
+ import { getTransport } from '../transport/singleton.js';
3
3
  /**
4
4
  * Requests a navigation within civitai.com. The host mediates — `target:
5
5
  * "current"` navigates the parent frame; `"new_tab"` opens a new tab (which
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
- import { HUMAN_INTERACTION_TIMEOUT_MS } from '../internal/requestTimeouts.js';
3
- import { getTransport } from '../internal/singleton.js';
4
- import { RequestTimeoutError, sendTypedRequest } from '../internal/transport.js';
2
+ import { HUMAN_INTERACTION_TIMEOUT_MS } from '../transport/requestTimeouts.js';
3
+ import { getTransport } from '../transport/singleton.js';
4
+ import { RequestTimeoutError, sendTypedRequest } from '../transport/transport.js';
5
5
  /**
6
6
  * The closed set of HOST refusal codes, as a runtime Set.
7
7
  *
@@ -158,7 +158,7 @@ export function useCollectionFollow() {
158
158
  type: 'SET_COLLECTION_FOLLOW',
159
159
  payload: { collectionId: args.collectionId, follow: args.follow },
160
160
  }, 'COLLECTION_FOLLOW_RESULT',
161
- // See the `'human'` bucketing in `internal/requestTimeouts.ts`: the
161
+ // See the `'human'` bucketing in `transport/requestTimeouts.ts`: the
162
162
  // host answers only when the viewer clicks or dismisses its confirm.
163
163
  { timeoutMs: HUMAN_INTERACTION_TIMEOUT_MS });
164
164
  if (reply.error || !reply.result) {
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
2
  import { armConsentRefusalLatch, clearConsentRefusalLatch, readConsentRefusalLatch, } from '../internal/consentRefusalLatch.js';
3
- import { getTransport } from '../internal/singleton.js';
4
- import { subscribeTyped } from '../internal/transport.js';
3
+ import { getTransport } from '../transport/singleton.js';
4
+ import { subscribeTyped } from '../transport/transport.js';
5
5
  /**
6
6
  * Subscribe to the host's `CONSENT_UNAVAILABLE` push — the signal that a
7
7
  * `REQUEST_CONSENT` this block sent can **never** be granted in this
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react';
2
- import { HUMAN_INTERACTION_TIMEOUT_MS } from '../internal/requestTimeouts.js';
3
- import { getTransport } from '../internal/singleton.js';
4
- import { RequestTimeoutError, sendTypedRequest } from '../internal/transport.js';
2
+ import { HUMAN_INTERACTION_TIMEOUT_MS } from '../transport/requestTimeouts.js';
3
+ import { getTransport } from '../transport/singleton.js';
4
+ import { RequestTimeoutError, sendTypedRequest } from '../transport/transport.js';
5
5
  /**
6
6
  * The closed set of HOST refusal codes, as a runtime Set.
7
7
  *
@@ -170,7 +170,7 @@ export function useCreatePostFromApp() {
170
170
  : {}),
171
171
  },
172
172
  }, 'CREATE_POST_RESULT',
173
- // See the `'human'` bucketing in `internal/requestTimeouts.ts`: the
173
+ // See the `'human'` bucketing in `transport/requestTimeouts.ts`: the
174
174
  // host answers only when the viewer clicks or dismisses its confirm.
175
175
  { timeoutMs: HUMAN_INTERACTION_TIMEOUT_MS });
176
176
  if (reply.error || !reply.result) {
@@ -34,8 +34,10 @@ export interface UseDailyCompensation {
34
34
  * `blocks.getMyDailyCompensation` mutation (scope `buzz:read:self`).
35
35
  *
36
36
  * Fetches on mount and whenever `params` change (by value), and exposes `refetch`.
37
- * A host that never answers surfaces as an `error` after the transport timeout;
38
- * late post-unmount responses are ignored.
37
+ * A host that never answers surfaces as an `error` after the transport timeout.
38
+ * Only the LATEST request may write state: a reply superseded by a newer
39
+ * `refetch` / params change — or one that lands after unmount — is dropped
40
+ * (#392).
39
41
  *
40
42
  * @example
41
43
  * const { resources, hasPublishedResources } = useDailyCompensation({ date: '2026-07-01' });
@@ -1,6 +1,7 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
2
- import { getTransport } from '../internal/singleton.js';
3
- import { sendTypedRequest } from '../internal/transport.js';
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { getTransport } from '../transport/singleton.js';
3
+ import { sendTypedRequest } from '../transport/transport.js';
4
+ import { useRequestSequencer } from './useRequestSequencer.js';
4
5
  /**
5
6
  * Read the signed-in viewer's per-modelVersion generation compensation for the
6
7
  * MONTH containing `params.date`, through the host-mediated
@@ -9,8 +10,10 @@ import { sendTypedRequest } from '../internal/transport.js';
9
10
  * `blocks.getMyDailyCompensation` mutation (scope `buzz:read:self`).
10
11
  *
11
12
  * Fetches on mount and whenever `params` change (by value), and exposes `refetch`.
12
- * A host that never answers surfaces as an `error` after the transport timeout;
13
- * late post-unmount responses are ignored.
13
+ * A host that never answers surfaces as an `error` after the transport timeout.
14
+ * Only the LATEST request may write state: a reply superseded by a newer
15
+ * `refetch` / params change — or one that lands after unmount — is dropped
16
+ * (#392).
14
17
  *
15
18
  * @example
16
19
  * const { resources, hasPublishedResources } = useDailyCompensation({ date: '2026-07-01' });
@@ -20,23 +23,22 @@ export function useDailyCompensation(params) {
20
23
  const [hasPublishedResources, setHasPublishedResources] = useState(null);
21
24
  const [loading, setLoading] = useState(true);
22
25
  const [error, setError] = useState(null);
23
- const mountedRef = useRef(true);
24
- useEffect(() => {
25
- mountedRef.current = true;
26
- return () => {
27
- mountedRef.current = false;
28
- };
29
- }, []);
26
+ // Latest-wins + unmount guard in one predicate (#392): a reply may write state
27
+ // only if it answers the request this hook is CURRENTLY waiting for. A bare
28
+ // mount check would let a superseded request's slow reply overwrite newer
29
+ // state — nothing unmounted, so it passes.
30
+ const seq = useRequestSequencer();
30
31
  // Stable params key so `refetch`'s identity only changes when the params VALUE
31
32
  // changes; the callback re-parses it so it closes over nothing but the key.
32
33
  const paramsKey = JSON.stringify(params);
33
34
  const refetch = useCallback(() => {
35
+ const token = seq.begin();
34
36
  setLoading(true);
35
37
  setError(null);
36
38
  const parsed = JSON.parse(paramsKey);
37
39
  sendTypedRequest(getTransport(), { type: 'GET_DAILY_COMPENSATION', payload: { params: parsed } }, 'DAILY_COMPENSATION_RESULT')
38
40
  .then((result) => {
39
- if (!mountedRef.current)
41
+ if (!seq.isCurrent(token))
40
42
  return;
41
43
  if (result.error || !result.result) {
42
44
  // `||`, not `??`: the reply validator gates `error` on SHAPE only, so a
@@ -51,12 +53,12 @@ export function useDailyCompensation(params) {
51
53
  setLoading(false);
52
54
  })
53
55
  .catch((err) => {
54
- if (!mountedRef.current)
56
+ if (!seq.isCurrent(token))
55
57
  return;
56
58
  setError(err instanceof Error ? err : new Error(String(err)));
57
59
  setLoading(false);
58
60
  });
59
- }, [paramsKey]);
61
+ }, [paramsKey, seq]);
60
62
  useEffect(() => {
61
63
  refetch();
62
64
  }, [refetch]);
@@ -1,3 +1,9 @@
1
+ /**
2
+ * What {@link useDirectLoad} returns: `true` once the block is known to be
3
+ * loaded directly rather than embedded. #380 — the options type has been
4
+ * exported since this hook shipped; the RETURN type had no name at all.
5
+ */
6
+ export type UseDirectLoad = boolean;
1
7
  export interface UseDirectLoadOptions {
2
8
  /**
3
9
  * Milliseconds to wait for `BLOCK_INIT` before treating a top-level load as a
@@ -28,5 +34,5 @@ export interface UseDirectLoadOptions {
28
34
  * Once `ready` flips it stays authoritative: this can never return `true` while
29
35
  * `ready` is `true`, so a late init can't leave a stuck fallback.
30
36
  */
31
- export declare function useDirectLoad(options?: UseDirectLoadOptions): boolean;
37
+ export declare function useDirectLoad(options?: UseDirectLoadOptions): UseDirectLoad;
32
38
  //# sourceMappingURL=useDirectLoad.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState } from 'react';
2
- import { DIRECT_LOAD_TIMEOUT_MS } from '../internal/directLoad.js';
2
+ import { DIRECT_LOAD_TIMEOUT_MS } from '../transport/directLoad.js';
3
3
  import { useTransportSnapshot } from './useBlockContext.js';
4
4
  /**
5
5
  * True iff the current window is the TOP-LEVEL browsing context — i.e. the
@@ -1,7 +1,11 @@
1
1
  import type { ColorDomain } from '@civitai/app-sdk/blocks';
2
2
  /**
3
- * What {@link useDomainMaturity} returns.
3
+ * What {@link useDomainMaturity} returns. An alias for the long-standing
4
+ * {@link DomainMaturity} name, which stays exported and unchanged — see
5
+ * `./returnTypeLedger.js`.
4
6
  */
7
+ export type UseDomainMaturity = DomainMaturity;
8
+ /** The domain/viewer maturity projection {@link useDomainMaturity} exposes. */
5
9
  export interface DomainMaturity {
6
10
  /**
7
11
  * The color-domain the block is rendered inside (`green`|`blue`|`red`), or
@@ -92,5 +96,5 @@ export interface DomainMaturity {
92
96
  * const { maxBrowsingLevel, effectiveBrowsingLevel } = useDomainMaturity();
93
97
  * const hiddenByYourSettings = effectiveBrowsingLevel !== maxBrowsingLevel;
94
98
  */
95
- export declare function useDomainMaturity(): DomainMaturity;
99
+ export declare function useDomainMaturity(): UseDomainMaturity;
96
100
  //# sourceMappingURL=useDomainMaturity.d.ts.map