@civitai/blocks-react 0.43.0 → 0.44.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.
@@ -112,46 +112,57 @@ function sleep(ms, signal) {
112
112
  * reproduces the bug one layer down: a caller who logs `message` expecting the
113
113
  * server's words gets a constant and is exactly as stuck as before.
114
114
  *
115
- * - {@link WorkflowEstimateError.snapshot}`.error` — the server's own words,
116
- * verbatim. **Server-authored and UNSANITISED**: civitai's `errorHandling.ts`
117
- * documents that raw upstream text — Prisma/`pg` column and constraint names
118
- * among it — can reach this field. Log it, or show it in a developer-facing
119
- * error surface; do NOT render it as trusted copy.
120
- * - {@link WorkflowEstimateError.message} — a CONSTANT template with only
121
- * `code` interpolated. It contains no server text and nothing to sanitise, so
122
- * it is safe to print. That is deliberate: `message` is what an uncaught
115
+ * 🔴 THREE FIELDS, THREE AUDIENCES — AND **NONE OF THEM IS VIEWER-FACING COPY**.
116
+ * The viewer-facing string is one the APP owns; this error carries nothing you
117
+ * may render as-is. Two apps migrating to `0.43.0` each had a `catch` that piped
118
+ * `err.message` straight into rendered UI, and each would have shipped
119
+ * `estimate did not return a usable price (failed) — reason on .snapshot.error`
120
+ * to end users; both were caught only by a test asserting that exact string.
121
+ *
122
+ * - {@link WorkflowEstimateError.snapshot}`.error` — **the DIAGNOSTIC read.**
123
+ * The server's own words, verbatim. **Server-authored and UNSANITISED**:
124
+ * civitai's `errorHandling.ts` documents that raw upstream text —
125
+ * Prisma/`pg` column and constraint names among it — can reach this field.
126
+ * Log it, or show it in a developer-facing error surface. **NEVER render it
127
+ * verbatim into markup**, and think before shipping it to a third-party
128
+ * error tracker.
129
+ * - {@link WorkflowEstimateError.message} — **DEVELOPER-FACING.** A CONSTANT
130
+ * template with only `code` interpolated. It contains no server text and
131
+ * nothing to sanitise, so it is safe to LOG and safe to let a stack trace
132
+ * print — that is deliberate, because `message` is what an uncaught
123
133
  * rejection prints and what a third-party block's error reporter ships
124
134
  * upstream by default, and this is a package consumed by third-party code.
125
- * Its exact wording is NOT a contract.
126
- * - {@link WorkflowEstimateError.code} — the only stable branch target.
135
+ * **It is NOT intended for display to viewers**: it names an internal field
136
+ * path, it is not localised, and **its exact wording is NOT a contract** —
137
+ * it can change in any release, so a UI built on it silently rots.
138
+ * - {@link WorkflowEstimateError.code} — **the BRANCH TARGET**, and the only
139
+ * stable one. Switch on it to pick a viewer-facing string YOUR APP owns
140
+ * (see the mapper in the `@example` on {@link useBuzzWorkflow}).
127
141
  *
128
142
  * 🔴 DO NOT "SIMPLIFY" THIS BY PUTTING `snapshot.error` BACK ON `message`. The
129
143
  * two fields are split on purpose and the split is what keeps database internals
130
144
  * off a third party's default-printed surface. `test/useBuzzWorkflow.test.tsx`
131
145
  * pins it with a realistic `Unique constraint failed: Key (email)=(…)` fixture.
132
146
  *
133
- * 🔴 NOT APPLIED TO `submit`, and the reason is narrower than it looks. On
134
- * `WORKFLOW_SUBMITTED` there are likewise TWO producers, indistinguishable by
135
- * `status`: budget/cap REJECTIONS, which are a documented outcome the block
136
- * recovers from (open a top-up flow) and which carry a `cost`; and caught server
137
- * exceptions posted via the SAME `failureSnapshot(err)` used above, which do not.
138
- * The `#4159` defect is therefore live on `submit` too, discriminated by `cost`
139
- * presence rather than by `status`. Fixing it there is a separate change with its
140
- * own blast radius on the recovery path (a fix MUST keep the budget-rejection arm
141
- * RESOLVING, or the top-up flow breaks) and is deliberately NOT attempted here —
142
- * so `submit` is left resolving BOTH, and the accompanying test pins only the
143
- * budget-rejection producer, not a claim that submit is correct.
144
- *
145
- * 🔴 TRACKED, NOT FORGOTTEN: civitai/civitai-app-starters#251. Read it before
146
- * touching `submit`'s failure handling.
147
+ * 🔴 `submit` HAS ITS OWN CLASS — {@link WorkflowSubmitError} — BECAUSE ITS
148
+ * DISCRIMINATOR IS DIFFERENT. On `WORKFLOW_SUBMITTED` both producers report
149
+ * `status:'failed'`, so `status` separates nothing there and `cost` presence is
150
+ * what does. Do not merge the two classes or reuse this one for submit: the
151
+ * question each answers is different ("was a usable price returned?" vs "was a
152
+ * workflow actually queued?") and so is the arm that must keep resolving.
153
+ * (civitai/civitai-app-starters#251, the `submit` half of civitai/civitai#4159.)
147
154
  */
148
155
  export class WorkflowEstimateError extends Error {
149
156
  /**
150
157
  * WHICH PRODUCER this was, structurally — so a caller branches on an enum
151
158
  * rather than on prose. It is the ONLY stable branch target here:
152
159
  * `snapshot.error` is server-authored and can change without notice, and
153
- * {@link WorkflowEstimateError.message} is a generic summary whose exact
154
- * wording is not a contract either.
160
+ * {@link WorkflowEstimateError.message} is a generic developer-facing summary
161
+ * whose exact wording is not a contract either.
162
+ *
163
+ * 🔴 THIS IS WHAT A VIEWER-FACING MESSAGE MUST BE DERIVED FROM. Branch on
164
+ * `code` and return a string your app owns and localises; never render
165
+ * `message` or `snapshot.error` to a viewer.
155
166
  *
156
167
  * - `'failed'` — the reply's `status` is `'failed'`. USUALLY that means
157
168
  * `blocks.estimateWorkflow` threw server-side and the host posted its
@@ -176,8 +187,8 @@ export class WorkflowEstimateError extends Error {
176
187
  * will not price — the whole point of civitai/civitai#4159), but civitai's
177
188
  * `errorHandling.ts` documents that raw upstream text — Prisma/`pg` column and
178
189
  * constraint names among it — can reach that field. Log it, show it in a
179
- * developer-facing error surface, do NOT render it as trusted copy and do not
180
- * ship it to a third-party error tracker without thinking about it.
190
+ * developer-facing error surface, **NEVER render it verbatim into markup**,
191
+ * and do not ship it to a third-party error tracker without thinking about it.
181
192
  */
182
193
  snapshot;
183
194
  constructor(snapshot, code) {
@@ -190,20 +201,245 @@ export class WorkflowEstimateError extends Error {
190
201
  // wire), so routing the raw text to `.snapshot.error` alone costs a
191
202
  // deliberate debugger nothing and changes only the DISPOSITION. `code` is
192
203
  // carried IN the message so an uncaught rejection is still self-describing.
204
+ //
205
+ // 🔴 IT IS DEVELOPER-FACING, NOT VIEWER-FACING — it names an internal field
206
+ // path and is not localised, so it must not reach rendered UI. Its exact
207
+ // wording is NOT a contract: it may change in any release. A viewer-facing
208
+ // string is derived from `code` by the app (see the class docs).
193
209
  super(`estimate did not return a usable price (${code}) — reason on .snapshot.error`);
194
210
  this.name = 'WorkflowEstimateError';
195
211
  this.code = code;
196
212
  this.snapshot = snapshot;
197
213
  }
198
214
  }
215
+ /**
216
+ * The `workflowId` the host stamps on a snapshot it SYNTHESISED itself, rather
217
+ * than one derived from a real orchestrator workflow.
218
+ *
219
+ * 🔴 THIS IS A REAL WIRE INVARIANT, NOT A HEURISTIC. Every reply the host builds
220
+ * itself goes through `failureSnapshot()` (civitai
221
+ * `src/components/AppBlocks/failureSnapshot.ts`), which hardcodes this exact
222
+ * literal — from its `catch` arms AND from non-catch short-circuits such as the
223
+ * moderator-review nack. The server's `snapshotFromWorkflow` instead returns
224
+ * `workflow.id ?? 'whatif'`.
225
+ *
226
+ * 🔴 COMPARE IT WITH `===` — EXACT AND CASE-SENSITIVE. Every looser comparison
227
+ * quietly reclassifies a reply the host did NOT synthesise into the reassuring
228
+ * "nothing to report" arm, and each direction has to be pinned separately:
229
+ * `.startsWith()` accepts `failed-x`, `.endsWith()` accepts `x-failed`, and a
230
+ * case-folding compare accepts `FAILED`. All three are WIDENINGS, which a
231
+ * delete-only mutation sweep cannot see. Pinned by three near-miss fixtures in
232
+ * `test/useBuzzWorkflow.test.tsx`; an earlier version of this comment said only
233
+ * "never a prefix test" while the tests pinned only that one direction — a
234
+ * description wider than its coverage.
235
+ *
236
+ * 🔴 THE FAIL-SAFETY IS ONE-DIRECTIONAL, AND SAYING OTHERWISE WAS A BUG IN AN
237
+ * EARLIER REVISION OF THIS COMMENT. An id that is NOT this literal is treated as
238
+ * possibly-charged, so an unrecognised id never buys the reassuring reading —
239
+ * that direction holds. The converse does NOT: this sentinel is also what the
240
+ * host stamps when a submit threw for reasons that may still have created and
241
+ * charged a workflow (a lost response, an in-progress idempotency conflict). So
242
+ * the sentinel arm means "the host had no workflow to report", never "nothing
243
+ * happened". See {@link WorkflowSubmitError.code}.
244
+ */
245
+ const HOST_SYNTHESISED_WORKFLOW_ID = 'failed';
246
+ /**
247
+ * Thrown by {@link UseBuzzWorkflowReturn.submit} when the host's reply carries no
248
+ * usable workflow outcome — either the submit ERRORED before anything was queued,
249
+ * or a workflow-shaped reply came back already failed with no price.
250
+ *
251
+ * 🔴 READ {@link WorkflowSubmitError.code} BEFORE SAYING ANYTHING ABOUT MONEY.
252
+ * The two codes differ on exactly that, and getting it wrong is expensive in both
253
+ * directions — see the per-code notes below.
254
+ *
255
+ * 🔴 WHY THIS EXISTS — civitai/civitai-app-starters#251, the `submit` half of
256
+ * civitai/civitai#4159. A server-side `blocks.submitWorkflow` throw cannot reach
257
+ * the block as a rejection; the reply crosses `postMessage`. The host instead
258
+ * posts a well-formed `WORKFLOW_SUBMITTED` carrying its `failureSnapshot(err)` —
259
+ * `{ workflowId: 'failed', status: 'failed', error: '<server message>' }`, with
260
+ * **no `cost`**. That snapshot is perfectly legal, so `submit()` resolved it and
261
+ * moved the hook to `'done'`, handing the caller a "workflow" that does not
262
+ * exist.
263
+ *
264
+ * 🔴 TWO PRODUCERS, ONE `status` — AND HERE `status` DISCRIMINATES NOTHING. This
265
+ * is what makes submit's version of the defect different from estimate's, and
266
+ * why {@link WorkflowEstimateError}'s guard could not simply be copied:
267
+ *
268
+ * - **budget / spend-cap REJECTION** — a legitimate OUTCOME. The server quotes
269
+ * the price it refused to charge, so the snapshot carries a numeric
270
+ * `cost.total`. Blocks recover from it (open a top-up flow), so it MUST keep
271
+ * RESOLVING. Every such exit on the server attaches a cost: the per-call
272
+ * `buzzBudget` gate, the per-user daily Buzz cap, the per-app aggregate and
273
+ * velocity caps, and the dev-tunnel session cap — on all three body kinds.
274
+ * - **caught server EXCEPTION** — `failureSnapshot(err)`, no `cost`, and the
275
+ * `'failed'` sentinel id. The host had no workflow to report — usually
276
+ * nothing was queued, but a lost response or an in-progress idempotency
277
+ * conflict lands here too. Rejects as `'exception'`.
278
+ * - **a reply that came back failed and unpriced with a NON-sentinel id** —
279
+ * normally a genuine orchestrator id, no `cost` (the server's
280
+ * `snapshotFromWorkflow` omits the key whenever `workflow.cost?.total` is not
281
+ * numeric). Rejects as `'workflow-failed'`, and **money may already be
282
+ * committed** — see that code's note.
283
+ *
284
+ * So the rule is `cost` presence, and ONLY among failure-shaped replies: an
285
+ * ordinary in-flight submit (`{ workflowId:'wf_…', status:'pending' }`) is
286
+ * cost-less too and must keep resolving, which is why the guard is not keyed on
287
+ * `cost` alone either. Both clauses are load-bearing; each has its own control
288
+ * in `test/useBuzzWorkflow.test.tsx`.
289
+ *
290
+ * 🔴 THE GUARD IS DELIBERATELY NOT WIDENED TO ALL TERMINAL STATUSES. A cost-less
291
+ * `succeeded` / `canceled` / `expired` reply is a legitimate outcome and must
292
+ * RESOLVE; only `'failed'` is unusable. Swapping the clause for
293
+ * `TERMINAL_STATUSES.has(...)` is a silent WIDENING that a delete-only mutation
294
+ * sweep cannot see — `test/useBuzzWorkflow.test.tsx` pins each of those three
295
+ * statuses explicitly for exactly that reason.
296
+ *
297
+ * 🔴 THREE FIELDS, THREE AUDIENCES — AND **NONE OF THEM IS VIEWER-FACING COPY**,
298
+ * exactly as on {@link WorkflowEstimateError}. Two apps migrating to `0.43.0`
299
+ * piped that class's `err.message` straight into rendered UI
300
+ * (civitai/civitai-app-starters#253); the same split applies here so the same
301
+ * mistake is not re-enabled on the submit path.
302
+ *
303
+ * - {@link WorkflowSubmitError.snapshot}`.error` — **the DIAGNOSTIC read.** The
304
+ * server's own words, verbatim. **Server-authored and UNSANITISED**:
305
+ * civitai's `errorHandling.ts` documents that raw upstream text —
306
+ * Prisma/`pg` column and constraint names among it — can reach this field.
307
+ * Log it, or show it in a developer-facing error surface. **NEVER render it
308
+ * verbatim into markup**, and think before shipping it to a third-party
309
+ * error tracker.
310
+ * - {@link WorkflowSubmitError.message} — **DEVELOPER-FACING.** A CONSTANT
311
+ * template with only `code` interpolated. It contains no server text, so it
312
+ * is safe to LOG and safe to let a stack trace print — deliberate, because
313
+ * `message` is what an uncaught rejection prints and what a third-party
314
+ * block's error reporter ships upstream by default. **It is NOT intended for
315
+ * display to viewers** and **its exact wording is NOT a contract.**
316
+ * - {@link WorkflowSubmitError.code} — **the BRANCH TARGET**, and the only
317
+ * stable one.
318
+ *
319
+ * 🔴 DO NOT "SIMPLIFY" THIS BY PUTTING `snapshot.error` BACK ON `message`, and do
320
+ * not widen the guard to reject a budget rejection. The first re-opens #253; the
321
+ * second breaks the top-up recovery flow, which is the one thing #251 says must
322
+ * not break.
323
+ */
324
+ export class WorkflowSubmitError extends Error {
325
+ /**
326
+ * WHICH PRODUCER this was, structurally — so a caller branches on an enum
327
+ * rather than on prose. It is the ONLY stable branch target here:
328
+ * `snapshot.error` is server-authored and can change without notice, and
329
+ * {@link WorkflowSubmitError.message} is a generic developer-facing summary
330
+ * whose exact wording is not a contract either.
331
+ *
332
+ * 🔴 THE TWO CODES DIFFER ON WHETHER MONEY MOVED. Do not collapse them, and do
333
+ * not write recovery copy that ignores the distinction.
334
+ *
335
+ * - `'exception'` — the host built this reply itself
336
+ * (`failureSnapshot(err)`, id {@link HOST_SYNTHESISED_WORKFLOW_ID}), from a
337
+ * `catch` OR from a non-catch short-circuit such as the moderator-review
338
+ * nack. **It means the host had no workflow to report — NOT that nothing
339
+ * happened.**
340
+ *
341
+ * 🔴 USUALLY nothing was queued and nothing was charged, and a retry is the
342
+ * sensible recovery. But the same shape is reachable when a workflow MAY have
343
+ * been created and charged, because the failure happened after the request
344
+ * left the block:
345
+ * - a **lost response** — the server's own catch concedes that "the
346
+ * orchestrator externalId dedupe still protects a retry that DID create a
347
+ * workflow server-side despite a lost response". What that path refunds
348
+ * is its own reservation and cap counters; the Buzz debit happens inside
349
+ * the orchestrator's submit.
350
+ * - an **in-progress idempotency CONFLICT** — a concurrent first attempt is
351
+ * still in flight and may be committing right now. Retrying blind is the
352
+ * worst option there.
353
+ * - a **transient transport failure** (5xx / 408 / 429 / 401), which the
354
+ * `dev:live` host collapses into this same shape.
355
+ *
356
+ * So prefer **reusing the same {@link SubmitWorkflowOptions.idempotencyKey}**
357
+ * on retry rather than letting a fresh one be minted, and do not render
358
+ * "nothing was charged" to a viewer as a certainty. There is no workflow id
359
+ * on this arm to poll.
360
+ *
361
+ * - `'workflow-failed'` — the id was NOT the host's sentinel, so the reply came
362
+ * from the server's `snapshotFromWorkflow`: normally a real orchestrator id,
363
+ * already `'failed'` and with no price.
364
+ *
365
+ * 🔴 **DO NOT TELL THE VIEWER NOTHING WAS CHARGED, AND DO NOT BLIND-RETRY.**
366
+ * Server-side, `blocks.submitWorkflow` treats *any resolved* submit as
367
+ * money-COMMITTED: its own comment is "A resolved submit is money-COMMITTED
368
+ * (the reservation is kept regardless of snapshot status)… we do NOT refund
369
+ * on a non-throwing failed snapshot", and `finalizeGenIdempotency` runs on
370
+ * that path. So Buzz may already be spent for this call.
371
+ *
372
+ * A retry is a SECOND reservation unless you reuse the same
373
+ * {@link SubmitWorkflowOptions.idempotencyKey} — `submit()` mints a fresh key
374
+ * per call by default, so an automatic retry double-reserves.
375
+ *
376
+ * 🔴 `err.snapshot.workflowId` IS USUALLY POLLABLE, BUT CHECK IT FIRST — this
377
+ * arm is defined by what the id is NOT. The server emits `workflow.id ??
378
+ * 'whatif'`, and it treats **both** `'failed'` and `'whatif'` as non-workflow
379
+ * sentinels (it skips its own persistence and settle steps on either). So
380
+ * `'whatif'` lands here — correctly, because the cautious money reading still
381
+ * applies — but there is nothing to poll. Guard with
382
+ * `err.snapshot.workflowId !== 'whatif'` before calling
383
+ * {@link UseBuzzWorkflowReturn.watch} / {@link UseBuzzWorkflowReturn.poll} to
384
+ * learn the workflow's actual fate before spending again.
385
+ *
386
+ * A union rather than a boolean so a future producer gets its own code without
387
+ * breaking a caller's `switch`.
388
+ *
389
+ * 🔴 A BUDGET REJECTION NEVER ARRIVES HERE. It resolves, and is read off the
390
+ * returned snapshot as `status === 'failed'` with a numeric `cost.total`.
391
+ */
392
+ code;
393
+ /**
394
+ * The snapshot the host replied with, VERBATIM — including `snapshot.error`,
395
+ * the server's own words, when there are any.
396
+ *
397
+ * 🔴 `snapshot.error` IS SERVER-AUTHORED AND UNSANITISED. It is where the
398
+ * reason lives, but raw upstream text — Prisma/`pg` column and constraint
399
+ * names among it — can reach that field. Log it, show it in a developer-facing
400
+ * error surface, **NEVER render it verbatim into markup**.
401
+ */
402
+ snapshot;
403
+ constructor(snapshot, code) {
404
+ // 🔴 GENERIC, AND DELIBERATELY NOT `snapshot.error` — see the class docs and
405
+ // civitai/civitai-app-starters#253. `message` is the field an uncaught
406
+ // rejection PRINTS and the field a third-party block's error reporter ships
407
+ // upstream by default, so putting the raw server string here would put
408
+ // database internals on the default-printed surface of somebody else's code.
409
+ // Exposure to the block is identical either way (`snapshot.error` was always
410
+ // on the wire); only the DISPOSITION changes. `code` is carried IN the
411
+ // message so an uncaught rejection is still self-describing.
412
+ //
413
+ // 🔴 IT MAKES NO CLAIM ABOUT MONEY, AND THAT IS THE POINT. An earlier draft
414
+ // read "submit did not queue a workflow", which is FALSE for
415
+ // `'workflow-failed'` — that arm has a workflow-shaped reply and
416
+ // possibly-committed
417
+ // spend. A single template that is true for BOTH codes cannot mislead a
418
+ // developer reading a stack trace; the money semantics live on `code`.
419
+ super(`submit did not return a usable workflow (${code}) — reason on .snapshot.error`);
420
+ this.name = 'WorkflowSubmitError';
421
+ this.code = code;
422
+ this.snapshot = snapshot;
423
+ }
424
+ }
199
425
  /**
200
426
  * Orchestrates the estimate → confirm → submit → poll dance through the
201
427
  * host-mediated `postMessage` path.
202
428
  *
203
- * The host enforces budget rules (`cost_estimate <= token.buzzBudget`)
204
- * before forwarding to the orchestrator; submit() will reject if the host
205
- * refuses. Block apps should call `useBuzzPurchase().openPurchaseModal()`
206
- * when that happens.
429
+ * The host enforces budget rules (`cost_estimate <= token.buzzBudget`) before
430
+ * forwarding to the orchestrator.
431
+ *
432
+ * 🔴 A BUDGET REFUSAL DOES **NOT** REJECT — IT RESOLVES. It comes back as a
433
+ * snapshot with `status: 'failed'`, an `error` string and the `cost` the server
434
+ * declined to charge, and THAT resolved shape is the cue to call
435
+ * `useBuzzPurchase().openPurchaseModal()`. What DOES reject is a submit with no
436
+ * usable outcome — see {@link WorkflowSubmitError}. Routing a rejection into a
437
+ * top-up sells Buzz for a failure Buzz cannot fix.
438
+ *
439
+ * 🔴 NOR IS EVERY RESOLVED `'failed'` AN AFFORDABILITY PROBLEM. The per-app
440
+ * velocity limit, the per-app aggregate daily cap, a fail-closed "temporarily
441
+ * unavailable" deny and a missing price quote are all priced, resolving outcomes
442
+ * too. Branch on the message/your own policy before offering to sell anything.
207
443
  *
208
444
  * AFTER `submit` FLIPS `status` TO `'polling'`, USE `watch(workflowId)`. It owns
209
445
  * the loop, resolves on the terminal snapshot, and pushes every intermediate
@@ -246,21 +482,58 @@ export class WorkflowEstimateError extends Error {
246
482
  * modelVersionId,
247
483
  * params: { prompt: 'a cat' },
248
484
  * };
485
+ * // A viewer-facing string YOUR APP owns, chosen by `code`. Neither
486
+ * // `err.message` (developer-facing, wording not a contract) nor
487
+ * // `err.snapshot.error` (server-authored, unsanitised) may be rendered.
488
+ * const estimateFailureMessage = (err: WorkflowEstimateError) =>
489
+ * err.code === 'no-cost'
490
+ * ? 'We could not get a price for this configuration. Try adjusting it.'
491
+ * : 'Pricing is unavailable right now. Please try again shortly.';
492
+ *
249
493
  * // 🔴 estimate() REJECTS when the reply carries no usable price — an errored
250
494
  * // estimate, or one that came back with no numeric cost. ALWAYS catch it.
251
495
  * try {
252
496
  * await estimate(body); // status 'estimating' → 'confirming' (cost in result.cost.total)
253
497
  * } catch (err) {
254
498
  * // status is now 'error'; `result` holds the unusable snapshot, never a
255
- * // STALE priced one. Branch on err.code ('failed' | 'no-cost'); the server's
256
- * // reason is on err.snapshot.error (unsanitised — log it, don't render it as
257
- * // trusted copy). See WorkflowEstimateError.
499
+ * // STALE priced one. See WorkflowEstimateError for the three-audience split.
258
500
  * if (!(err instanceof WorkflowEstimateError)) throw err;
259
- * logForDebugging(err.snapshot.error);
260
- * showError('This configuration cannot be priced right now.');
501
+ * logForDebugging(err.message, err.snapshot.error); // developer-facing: LOG only
502
+ * showError(estimateFailureMessage(err)); // viewer-facing: app-owned
503
+ * }
504
+ * // 🔴 submit() REJECTS when the reply carries no usable workflow outcome, so it
505
+ * // needs a catch too — and the two codes differ on whether MONEY MOVED.
506
+ * try {
507
+ * const snap = await submit(body); // status 'submitting' → 'polling'
508
+ * if (snap.status === 'failed') {
509
+ * // RESOLVED + priced = a server outcome (affordability, a cap, a velocity
510
+ * // limit, a transient deny). Only some of those are fixable by buying Buzz.
511
+ * showError(submitOutcomeMessage(snap));
512
+ * } else {
513
+ * const done = await watch(snap.workflowId, { onUpdate: render }); // → terminal
514
+ * if (done.status === 'succeeded') setImages(done.imageUrls ?? []);
515
+ * }
516
+ * } catch (err) {
517
+ * if (!(err instanceof WorkflowSubmitError)) throw err;
518
+ * logForDebugging(err.message, err.snapshot.error); // developer-facing: LOG only
519
+ * // 🔴 TWO SEPARATE QUESTIONS: `code` decides what you may say about MONEY,
520
+ * // the id decides only whether there is anything to POLL. Conjoining them
521
+ * // sends a 'whatif' id into the reassuring arm.
522
+ * if (err.code === 'workflow-failed') {
523
+ * // Spend may already be committed. Do NOT retry blindly (that mints a new
524
+ * // idempotency key = a second reservation) and do NOT claim nothing was
525
+ * // charged.
526
+ * showError('The generation may have started but did not complete.');
527
+ * // 'whatif' is a non-workflow sentinel — nothing behind it to poll.
528
+ * if (err.snapshot.workflowId !== 'whatif') {
529
+ * await watch(err.snapshot.workflowId, { onUpdate: render });
530
+ * }
531
+ * } else {
532
+ * // 'exception' — usually nothing was queued. Retry, but reuse the SAME
533
+ * // idempotencyKey: a lost response can also land here.
534
+ * showError('Could not start the generation. Please try again.');
535
+ * }
261
536
  * }
262
- * const snap = await submit(body); // status 'submitting' → 'polling'; returns a workflowId
263
- * const done = await watch(snap.workflowId, { onUpdate: render }); // → terminal
264
537
  */
265
538
  export function useBuzzWorkflow() {
266
539
  const [status, setStatus] = useState('idle');
@@ -324,7 +597,45 @@ export function useBuzzWorkflow() {
324
597
  const idempotencyKey = options?.idempotencyKey ?? generateIdempotencyKey();
325
598
  try {
326
599
  const { snapshot } = await sendTypedRequest(getTransport(), { type: 'SUBMIT_WORKFLOW', payload: { body, idempotencyKey } }, 'WORKFLOW_SUBMITTED', { timeoutMs: WORKFLOW_REQUEST_TIMEOUT_MS });
600
+ // 🔴 PUBLISH THE SNAPSHOT BEFORE THE REJECTION BELOW, for the same reason
601
+ // `estimate` does: a block may render from `result` rather than from the
602
+ // returned value, and jumping over this line would leave the PREVIOUS
603
+ // submit's snapshot in place — a live control pointing at a workflow THIS
604
+ // submit did not queue.
327
605
  setResult(snapshot);
606
+ // 🔴 AN ERRORED SUBMIT MUST REJECT (civitai/civitai-app-starters#251, the
607
+ // `submit` half of civitai/civitai#4159). Two producers report
608
+ // `status:'failed'` and `status` separates neither:
609
+ //
610
+ // - a budget / spend-cap REJECTION is an OUTCOME the block recovers from
611
+ // (open a top-up flow). The server quotes the price it refused to
612
+ // charge, so `cost.total` is present. It RESOLVES — turning this arm
613
+ // into a throw is the one change that would break the recovery path.
614
+ // - a failure-shaped reply with NO `cost` is not a usable outcome. It
615
+ // REJECTS — and the `code` says which kind, because they differ on
616
+ // whether money moved (see WorkflowSubmitError.code).
617
+ //
618
+ // BOTH clauses are load-bearing. Dropping `status === 'failed'` would
619
+ // reject every ordinary in-flight reply (`{status:'pending'}` is cost-less
620
+ // too); dropping the cost test would reject the budget rejection. And the
621
+ // test is `typeof … !== 'number'`, never `!snapshot.cost?.total`: `0` is a
622
+ // real price and falsy.
623
+ //
624
+ // 🔴 `status === 'failed'`, NOT `TERMINAL_STATUSES.has(status)`. Widening it
625
+ // would reject cost-less `succeeded`/`canceled`/`expired` replies, which are
626
+ // legitimate outcomes the server really does emit without a price
627
+ // (`snapshotFromWorkflow` omits `cost` on any non-numeric total). That
628
+ // WIDENING is invisible to a mutation sweep that only deletes clauses, so
629
+ // all three statuses are pinned by their own fixtures in the test file.
630
+ if (snapshot.status === 'failed' && typeof snapshot.cost?.total !== 'number') {
631
+ // 🔴 WHICH ARM: the host stamps its own synthesised failures with the
632
+ // `'failed'` sentinel, while a server-built reply carries `workflow.id`
633
+ // (or the `'whatif'` sentinel).
634
+ // Anything unrecognised falls to `'workflow-failed'`, the arm that assumes
635
+ // money MAY be committed — an unknown id must never buy the reassuring
636
+ // "nothing was charged" reading.
637
+ throw new WorkflowSubmitError(snapshot, snapshot.workflowId === HOST_SYNTHESISED_WORKFLOW_ID ? 'exception' : 'workflow-failed');
638
+ }
328
639
  setStatus(TERMINAL_STATUSES.has(snapshot.status) ? 'done' : 'polling');
329
640
  return snapshot;
330
641
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useBuzzWorkflow.js","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useBuzzWorkflow.js","sourceRoot":"","sources":["../../src/hooks/useBuzzWorkflow.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckpointPicker.d.ts","sourceRoot":"","sources":["../../src/hooks/useCheckpointPicker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAMnE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,IAAI;IACrC,IAAI,EAAE,CAAC,IAAI,EAAE;QACX;;;;;WAKG;QACH,cAAc,EAAE,MAAM,CAAC;QACvB,uEAAuE;QACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,KAAK,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAAC;IAClD,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAkCA"}
1
+ {"version":3,"file":"useCheckpointPicker.d.ts","sourceRoot":"","sources":["../../src/hooks/useCheckpointPicker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAMnE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,IAAI;IACrC,IAAI,EAAE,CAAC,IAAI,EAAE;QACX;;;;;WAKG;QACH,cAAc,EAAE,MAAM,CAAC;QACvB,uEAAuE;QACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,KAAK,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,mBAAmB,CAAA;KAAE,CAAC,CAAC;IAClD,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAuCA"}
@@ -38,8 +38,13 @@ export function useCheckpointPicker() {
38
38
  }, []);
39
39
  const persist = useCallback(async (versionId) => {
40
40
  const { ok, error } = await sendTypedRequest(getTransport(), { type: 'SET_USER_CHECKPOINT', payload: { versionId } }, 'USER_CHECKPOINT_SET');
41
- if (!ok) {
42
- throw new Error(error ?? 'failed to persist checkpoint');
41
+ // A PRESENT `error` is the reject signal, not a TRUTHY one: the reply
42
+ // validator early-accepts anything carrying an `error` key, so `error: ''`
43
+ // reaches here having skipped the success-field checks. This site tested
44
+ // `!ok` ALONE before, so the `error` clause is new, not merely retyped.
45
+ // `||` (not `??`) so an empty error string still yields readable copy.
46
+ if (!ok || error !== undefined) {
47
+ throw new Error(error || 'failed to persist checkpoint');
43
48
  }
44
49
  }, []);
45
50
  return { open, persist };
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckpointPicker.js","sourceRoot":"","sources":["../../src/hooks/useCheckpointPicker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAIpC,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,mBAAmB;IAcjC,MAAM,IAAI,GAAG,WAAW,CACtB,KAAK,EAAE,IAA2D,EAAE,EAAE;QACpE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,gBAAgB,CACzC,YAAY,EAAE,EACd;YACE,IAAI,EAAE,wBAAwB;YAC9B,OAAO,EAAE;gBACP,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,IAAI;oBAC/B,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE;oBAC7C,CAAC,CAAC,EAAE,CAAC;aACR;SACF,EACD,0BAA0B,EAC1B,EAAE,SAAS,EAAE,4BAA4B,EAAE,CAC5C,CAAC;QACF,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,SAAwB,EAAE,EAAE;QAC7D,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,MAAM,gBAAgB,CAC1C,YAAY,EAAE,EACd,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,EAAE,EACvD,qBAAqB,CACtB,CAAC;QACF,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,IAAI,KAAK,CAAC,KAAK,IAAI,8BAA8B,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"useCheckpointPicker.js","sourceRoot":"","sources":["../../src/hooks/useCheckpointPicker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAIpC,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,mBAAmB;IAcjC,MAAM,IAAI,GAAG,WAAW,CACtB,KAAK,EAAE,IAA2D,EAAE,EAAE;QACpE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,gBAAgB,CACzC,YAAY,EAAE,EACd;YACE,IAAI,EAAE,wBAAwB;YAC9B,OAAO,EAAE;gBACP,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,IAAI;oBAC/B,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE;oBAC7C,CAAC,CAAC,EAAE,CAAC;aACR;SACF,EACD,0BAA0B,EAC1B,EAAE,SAAS,EAAE,4BAA4B,EAAE,CAC5C,CAAC;QACF,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,SAAwB,EAAE,EAAE;QAC7D,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,MAAM,gBAAgB,CAC1C,YAAY,EAAE,EACd,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,EAAE,EACvD,qBAAqB,CACtB,CAAC;QACF,sEAAsE;QACtE,2EAA2E;QAC3E,yEAAyE;QACzE,wEAAwE;QACxE,uEAAuE;QACvE,IAAI,CAAC,EAAE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,KAAK,IAAI,8BAA8B,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useSaveImage.d.ts","sourceRoot":"","sources":["../../src/hooks/useSaveImage.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;OASG;IACH,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAiB3C"}
1
+ {"version":3,"file":"useSaveImage.d.ts","sourceRoot":"","sources":["../../src/hooks/useSaveImage.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;OASG;IACH,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAqB3C"}
@@ -18,8 +18,12 @@ export function useSaveImage() {
18
18
  ? { url: input.url, filename: input.filename }
19
19
  : { imageId: input.imageId, filename: input.filename };
20
20
  const reply = await sendTypedRequest(getTransport(), { type: 'SAVE_IMAGE', payload }, 'SAVE_IMAGE_RESULT');
21
- if (!reply.ok || reply.error) {
22
- throw new Error(reply.error ?? 'failed to save image');
21
+ // A PRESENT `error` is the reject signal, not a TRUTHY one: the reply
22
+ // validator early-accepts anything carrying an `error` key, so `error: ''`
23
+ // reaches here having skipped the success-field checks. `||` (not `??`) so
24
+ // an empty error string still yields readable copy.
25
+ if (!reply.ok || reply.error !== undefined) {
26
+ throw new Error(reply.error || 'failed to save image');
23
27
  }
24
28
  }, []);
25
29
  return { saveImage };
@@ -1 +1 @@
1
- {"version":3,"file":"useSaveImage.js","sourceRoot":"","sources":["../../src/hooks/useSaveImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAgC5D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,EAAE,KAAqB,EAAiB,EAAE;QAC3E,MAAM,OAAO,GACX,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;YACvC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;YAC9C,CAAC,CAAC,EAAE,OAAO,EAAG,KAA6B,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;QACpF,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAClC,YAAY,EAAE,EACd,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,EAC/B,mBAAmB,CACpB,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,sBAAsB,CAAC,CAAC;QACzD,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC"}
1
+ {"version":3,"file":"useSaveImage.js","sourceRoot":"","sources":["../../src/hooks/useSaveImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAgC5D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,EAAE,KAAqB,EAAiB,EAAE;QAC3E,MAAM,OAAO,GACX,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;YACvC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;YAC9C,CAAC,CAAC,EAAE,OAAO,EAAG,KAA6B,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;QACpF,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAClC,YAAY,EAAE,EACd,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,EAC/B,mBAAmB,CACpB,CAAC;QACF,sEAAsE;QACtE,2EAA2E;QAC3E,2EAA2E;QAC3E,oDAAoD;QACpD,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,sBAAsB,CAAC,CAAC;QACzD,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useSharedStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useSharedStorage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAyB,MAAM,yBAAyB,CAAC;AAKzF;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB;;;;;;OAMG;IACH,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC9B;;;;;;;OAOG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACjD;;;;;;;OAOG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACnE;AA8CD,wBAAgB,gBAAgB,IAAI,gBAAgB,CA8GnD"}
1
+ {"version":3,"file":"useSharedStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useSharedStorage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAyB,MAAM,yBAAyB,CAAC;AAKzF;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB;;;;;;OAMG;IACH,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC9B;;;;;;;OAOG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACjD;;;;;;;OAOG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D;;;;;;;;;OASG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D;;;OAGG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACnE;AA8CD,wBAAgB,gBAAgB,IAAI,gBAAgB,CA8HnD"}
@@ -72,8 +72,12 @@ export function useSharedStorage() {
72
72
  },
73
73
  async report(key, reason) {
74
74
  const result = await sendTypedRequest(transport, { type: 'SHARED_REPORT', payload: { key, reason } }, 'SHARED_REPORT_RESULT');
75
- if (!result.ok || result.error) {
76
- throw new Error(result.error ?? 'shared report failed');
75
+ // A PRESENT `error` is the reject signal, not a TRUTHY one: the reply
76
+ // validator early-accepts anything carrying an `error` key, so
77
+ // `error: ''` reaches here having skipped the success-field checks.
78
+ // `||` (not `??`) so an empty error string still yields readable copy.
79
+ if (!result.ok || result.error !== undefined) {
80
+ throw new Error(result.error || 'shared report failed');
77
81
  }
78
82
  },
79
83
  async getCount(key) {
@@ -96,8 +100,9 @@ export function useSharedStorage() {
96
100
  },
97
101
  async update(key, value) {
98
102
  const result = await sendTypedRequest(transport, { type: 'SHARED_UPDATE', payload: { key, value } }, 'SHARED_UPDATE_RESULT');
99
- if (!result.ok || result.error) {
100
- throw new Error(result.error ?? 'shared update failed');
103
+ // PRESENT, not truthy — see `report()` above.
104
+ if (!result.ok || result.error !== undefined) {
105
+ throw new Error(result.error || 'shared update failed');
101
106
  }
102
107
  },
103
108
  async vote(key) {
@@ -117,6 +122,17 @@ export function useSharedStorage() {
117
122
  if (!result.ok || result.error) {
118
123
  throw new Error(result.error ?? 'shared withdraw failed');
119
124
  }
125
+ // `deleted` is optional on the wire type because an error reply omits
126
+ // it. On the success path the validator requires a boolean, so this is
127
+ // defence in depth rather than a reachable branch through the real
128
+ // transport — but it is what makes the `boolean` we return honest,
129
+ // instead of a cast asserting a guarantee this function cannot see.
130
+ // NOTE: this site still tests `error` for TRUTHINESS. It is not one of
131
+ // the six this change normalized (it early-accepted before PR #273);
132
+ // the remaining truthiness sites are a tracked follow-up.
133
+ if (typeof result.deleted !== 'boolean') {
134
+ throw new Error('shared withdraw failed: reply carried no `deleted` flag');
135
+ }
120
136
  return { ok: true, deleted: result.deleted };
121
137
  },
122
138
  };