@civitai/blocks-react 0.53.1 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,7 +27,7 @@ your block app and the SDK share a single React tree.
27
27
  import { useRef } from 'react';
28
28
  import { useBlockContext, useBlockResize, useBuzzWorkflow } from '@civitai/blocks-react';
29
29
  import { Button } from '@civitai/blocks-react/ui';
30
- import { isModelSlotContext } from '@civitai/app-sdk/blocks';
30
+ import { isModelSlotContext, isSignedIn } from '@civitai/app-sdk/blocks';
31
31
 
32
32
  export function App() {
33
33
  const { ready, context, viewer, theme } = useBlockContext();
@@ -45,7 +45,8 @@ export function App() {
45
45
  // GOTCHA #60: set data-theme on YOUR OWN root — the host can't reach into
46
46
  // the iframe to set it. Without this any [data-theme="dark"] CSS is dormant.
47
47
  <div ref={rootRef} data-theme={theme}>
48
- <p>Block for model {context.modelName} ({viewer ? 'signed in' : 'anon'})</p>
48
+ {/* Sign-in gate: call `isSignedIn`, never an identity read. */}
49
+ <p>Block for model {context.modelName} ({isSignedIn(viewer) ? 'signed in' : 'anon'})</p>
49
50
  {/* `/ui` Button — themed by the data-theme above; `loading` disables + shows a spinner */}
50
51
  <Button
51
52
  loading={status === 'submitting' || status === 'polling'}
@@ -113,7 +114,14 @@ const { ready, context, viewer, theme, settings, blockId, blockInstanceId, appId
113
114
 
114
115
  - `context` — `BlockContext` (`{ slotId, … }`); narrow to `ModelSlotContext` for
115
116
  model-page slots.
116
- - `viewer` — `ViewerInfo | null` (`null` = anonymous).
117
+ - `viewer` — `ViewerInfo | null` (`null` = anonymous). **Gate sign-in with
118
+ `isSignedIn(viewer)`** (from `@civitai/app-sdk/blocks`), never on
119
+ `viewer.id`/`viewer.username` (both `@deprecated`). Don't open-code the gate:
120
+ the SDK owns which spelling is correct — `signedIn` is optional on the wire
121
+ and is the one viewer field the init validator deliberately does not reject
122
+ when malformed, so `isSignedIn` answers from presence instead. Hover it for
123
+ the full reasoning. Need the identity itself? Use
124
+ [`useViewer()`](#useviewer) — scope-gated and audited per call.
117
125
  - `theme` — `'light' | 'dark'`. **Set `data-theme={theme}` on your root** (gotcha #60).
118
126
  LIVE: it starts at the `BLOCK_INIT` value and then tracks the host's
119
127
  `THEME_CHANGE` push when the viewer toggles dark mode mid-session — see
@@ -684,18 +692,72 @@ async function onCancel(id: string) {
684
692
 
685
693
  ### `useAppStorage()`
686
694
 
687
- Per-(block instance, viewer) KV datastore, host-mediated. 64 KB per value,
688
- 50 MB + ~1M rows per app.
695
+ KV datastore, host-mediated. Keys are **namespaced** per (block instance,
696
+ viewer); the byte and row **budgets** are enforced per (**app**, viewer), so
697
+ every instance of one app shares one budget for that viewer.
689
698
 
690
699
  ```tsx
700
+ import {
701
+ APP_STORAGE_MAX_VALUE_BYTES, // largest single value, in wire bytes
702
+ APP_STORAGE_MAX_BYTES, // total stored bytes per (app, viewer)
703
+ APP_STORAGE_MAX_ROWS, // total rows per (app, viewer)
704
+ } from '@civitai/app-sdk/blocks';
705
+
691
706
  const storage = useAppStorage();
692
- await storage.set('key', { any: 'json' }); // throws "PAYLOAD_TOO_LARGE" over a limit
707
+ await storage.set('key', { any: 'json' }); // rejects over ANY of the three ceilings
693
708
  const v = await storage.get<MyShape>('key'); // null if unset / anon
694
709
  await storage.delete('key'); // idempotent
695
710
  const { keys } = await storage.list({ prefix: 'note-' });
696
711
  const quota = await storage.getQuota(); // { usedBytes, rowCount, limitBytes, limitRows }
697
712
  ```
698
713
 
714
+ 🔴 **`getQuota()` is the authority; the constants are a snapshot.** These three
715
+ are the ceilings **as of the version of `@civitai/app-sdk` you installed** —
716
+ compiled-in figures, which is the same frozen-number failure mode this page
717
+ used to demonstrate, just with one copy instead of nine. The host can move a
718
+ ceiling without your lockfile changing. So:
719
+
720
+ - **Render `getQuota()`'s reply**, never a constant, anywhere a viewer sees a
721
+ number or a code path decides whether a write will fit.
722
+ - **Reach for the constants only where no quota reply is available** — a
723
+ build-time sanity check, a test fixture, a rough design-time estimate — and
724
+ treat the answer as "roughly, at install time".
725
+ - **Re-check after any SDK bump**, and expect movement: the per-viewer clamp
726
+ was sized against a measured distribution and the host says to expect a
727
+ re-measure. `appStorageLimits.ts` in `@civitai/app-sdk` carries the
728
+ provenance and a one-liner that re-derives the current values from the host.
729
+
730
+ Never hard-code a figure of your own: the docs here used to quote the app-wide
731
+ umbrella instead of the per-viewer clamp and were **25x** out on bytes and
732
+ **1000x** out on rows.
733
+
734
+ 🔴 **The ROW ceiling is usually the binding one, and a byte-based "x of y used"
735
+ readout will not see it coming.** A block caching one modest record per item a
736
+ viewer touches exhausts `limitRows` while still holding a small fraction of
737
+ `limitBytes`. Show rows too.
738
+
739
+ `createMockHost()` defaults to these same ceilings and enforces the per-value
740
+ cap, the byte budget and — since it was added — the **row** budget on write, so
741
+ a row-limit overrun now fails under `dev:mock` where it previously passed and
742
+ failed only in production. Pass `storage: { quotaBytes, limitRows }` to
743
+ simulate something smaller.
744
+
745
+ ⚠️ The mock is **not** gate-for-gate identical to the host. Three known
746
+ divergences:
747
+
748
+ - the error string a rejection carries
749
+ ([#343](https://github.com/civitai/civitai-app-starters/issues/343));
750
+ - the byte gate refusing a shrinking overwrite that the host admits
751
+ ([#345](https://github.com/civitai/civitai-app-starters/issues/345));
752
+ - 🔴 the byte gate counting **wire** bytes where the host counts **stored**
753
+ bytes — `octet_length(value::jsonb::text)`, larger for every container, up to
754
+ ~1.5x ([#347](https://github.com/civitai/civitai-app-starters/issues/347)).
755
+
756
+ Passing under `dev:mock` is evidence, not proof — and note the third one is
757
+ **permissive**: unlike the other two, it lets a write pass locally that
758
+ production will reject. Size your fixtures against `getQuota()`, not against
759
+ what the mock accepted.
760
+
699
761
  ### `useSharedStorage()`
700
762
 
701
763
  App-scoped, append-only, community-votable SHARED datastore (every viewer sees
@@ -1134,6 +1196,264 @@ For non-React or advanced use, the transport primitives are exported too:
1134
1196
  `readAllowedOriginsFromEnv`, `getTransport`, and `sendTypedRequest`. Hooks are the
1135
1197
  recommended surface; reach for these only when a hook doesn't fit.
1136
1198
 
1199
+ ## The `/testing` subexport
1200
+
1201
+ `@civitai/blocks-react/testing` is the **host-simulation** entry point: it stands
1202
+ in for civitai.com so your block can run in `vitest`/`happy-dom` and in a local
1203
+ dev harness. It is a normal, published subpath of a `0.x` package — see
1204
+ [Stability](#stability-of-testing) below for exactly what that does and does not
1205
+ promise.
1206
+
1207
+ **Everything on this subpath is a mock.** No network, no Buzz, no real backend.
1208
+ Until `0.55.0` that was not true: `createLiveHost`, which talks to the real
1209
+ Civitai backend and spends the token holder's own Buzz, was exported from here
1210
+ too, one autocomplete entry from `createMockHost`. It now lives on its own
1211
+ subpath — see [The `/live` subexport](#the-live-subexport) below.
1212
+
1213
+ ### The whole surface
1214
+
1215
+ This section is **the one place the surface is written down**, and it is not
1216
+ prose: `test/subpathSurfaces.test.ts` parses the two marked regions below and
1217
+ fails if they disagree with what `src/testing.tsx` actually exports — in either
1218
+ direction. Every other mention of this subpath (module docblock, `AGENTS.md`)
1219
+ points here rather than repeating the list, because a second copy is exactly
1220
+ what went stale in [#334](https://github.com/civitai/civitai-app-starters/issues/334).
1221
+
1222
+ **Values**
1223
+
1224
+ <!-- TESTING-SURFACE:VALUES:BEGIN -->
1225
+
1226
+ | Export | What it is |
1227
+ |---|---|
1228
+ | `resetTransport` | Drops the cached singleton transport. Call it in `beforeEach` so each test starts clean. |
1229
+ | `createMockHost` | A framework-agnostic fake of the embedding host — answers every `*_RESULT` message, with knobs for generation cost/latency/failure, Buzz balance, app + shared storage, consent, maturity. Returns a `MockHost`; call `.install()` and keep the returned teardown. **No network, no Buzz.** |
1230
+ | `readMockHostUrlOptions` | Reads the harness URL toggles (`?viewer` `?consent` `?fail` `?theme` `?pick` `?balance` `?latency` `?seed` …) into a `Partial<MockHostOptions>`. `Harness` applies it for you; call it directly only in a hand-rolled harness. |
1231
+ | `Harness` | The React wrapper: installs a `createMockHost` on mount, tears it down on unmount, and renders an optional on-screen outbound-message log. Takes every `MockHostOptions` field plus `applyUrlToggles` and `showLog`. |
1232
+
1233
+ <!-- TESTING-SURFACE:VALUES:END -->
1234
+
1235
+ **Types** — the transitive closure that makes those values nameable: each is the
1236
+ declared type of an option, of a `MockHost` member, or of a property of one of
1237
+ those, so you can hoist a sub-object out of an options literal and give it a
1238
+ type.
1239
+
1240
+ <!-- TESTING-SURFACE:TYPES:BEGIN -->
1241
+
1242
+ ```text
1243
+ CannedPick
1244
+ CostSpec
1245
+ HarnessProps
1246
+ ImageSpec
1247
+ MockBuzzBalance
1248
+ MockBuzzHandle
1249
+ MockBuzzScenario
1250
+ MockCannedImageScan
1251
+ MockGenerationScenario
1252
+ MockHost
1253
+ MockHostFailMode
1254
+ MockHostOptions
1255
+ MockHostScenarioPatch
1256
+ MockSharedScenario
1257
+ MockSharedSeed
1258
+ MockStorageScenario
1259
+ ```
1260
+
1261
+ <!-- TESTING-SURFACE:TYPES:END -->
1262
+
1263
+ That is the complete list. Nothing else is exported.
1264
+
1265
+ ### In a test
1266
+
1267
+ ```ts
1268
+ import {
1269
+ createMockHost,
1270
+ resetTransport,
1271
+ type MockHostOptions,
1272
+ type MockGenerationScenario,
1273
+ } from '@civitai/blocks-react/testing';
1274
+
1275
+ resetTransport();
1276
+
1277
+ // Hoisting a sub-object out of the options literal is why the scenario types
1278
+ // are exported.
1279
+ const generation: MockGenerationScenario = { costPerGen: 12, latencyMs: 0 };
1280
+ const options: MockHostOptions = { viewer: null, failMode: 'some', generation };
1281
+
1282
+ const host = createMockHost(options);
1283
+ const uninstall = host.install();
1284
+ host.setScenario({ failMode: 'none' }); // live-tune mid-test
1285
+ uninstall();
1286
+ ```
1287
+
1288
+ ### In a dev harness
1289
+
1290
+ ```tsx
1291
+ import { Harness } from '@civitai/blocks-react/testing';
1292
+
1293
+ export function DevRoot() {
1294
+ return (
1295
+ <Harness failMode="some" showLog>
1296
+ <App />
1297
+ </Harness>
1298
+ );
1299
+ }
1300
+ ```
1301
+
1302
+ `<Harness>` fires host messages from `window.location.origin`, and the transport
1303
+ drops inbound messages from origins outside its allowlist — so a dev harness
1304
+ must include its own origin, e.g. `VITE_BLOCK_ALLOWED_PARENT_ORIGINS=http://localhost:5173`.
1305
+ Otherwise `BLOCK_INIT` never lands.
1306
+
1307
+ ### Stability of `/testing`
1308
+
1309
+ [#334](https://github.com/civitai/civitai-app-starters/issues/334) offered a
1310
+ fork: *document the undocumented surface*, **or** *mark the subpath explicitly
1311
+ unstable*. This package took the **first** branch, and only the first. The
1312
+ section above is that documentation.
1313
+
1314
+ Concretely, and with no guarantee beyond what is actually enforced:
1315
+
1316
+ - **It is a normal subpath of a `0.x` package**, on the same footing as `.`,
1317
+ `./ui` and `./live` — no stronger, no weaker. Under semver `0.x`, a **minor
1318
+ may break it**. It is not `@internal`, and it is not "unsupported": fleet
1319
+ blocks import it from their dev harnesses and from their test suites.
1320
+ - **What is enforced** is that a change to the exported *symbol set* cannot ship
1321
+ silently. `test/subpathSurfaces.test.ts` fails on growth and on shrinkage, and
1322
+ it fails again unless the README section above is updated to match — so any
1323
+ such change is a deliberate edit that a reviewer sees and a changeset names.
1324
+ - **What is *not* promised** is the *shape* of the mock-host option and result
1325
+ types. They describe a fake host whose fidelity tracks the real one; a
1326
+ property may be added, tightened or renamed in a minor. The ledger asserts
1327
+ names, not shapes, and deliberately so.
1328
+
1329
+ What is *not* listed above is genuinely internal and carries no guarantee. Until
1330
+ `0.55.0` this subpath also re-exported 25 symbols with no documentation — the
1331
+ catalog client (`fetchCatalog`, `buildCatalogUrl`, `edgeThumb`, `modelToCard`,
1332
+ `DEFAULT_LIMIT`, …), the in-harness picker overlay (`openPickerOverlay`),
1333
+ `decodeBlockTokenPayload`, `disallowedAccountError`, `mockParentMessage`, and
1334
+ the `MockHostProvider` alias — plus `createLiveHost` / `LiveHostOptions`, which
1335
+ moved to `./live` rather than disappearing. See the `0.55.0` changelog entry for
1336
+ the full list and for the three removals that had a measured fleet consumer. If
1337
+ you were importing one of the internal ones, it lives at a path this package
1338
+ does not publish — open an issue rather than reaching into `dist/internal/`.
1339
+
1340
+ ## The `/live` subexport
1341
+
1342
+ > ### 🔴 `@civitai/blocks-react/live` spends real Buzz
1343
+ >
1344
+ > `createLiveHost` forwards the App-Block postMessage protocol to the **real
1345
+ > Civitai backend** over a pasted short-lived dev block token —
1346
+ > `blocks.submitWorkflow` included — and a successful generation **debits the
1347
+ > token holder's own Buzz**. There is no dry-run mode and no confirmation. It
1348
+ > exists for one caller: a `pnpm dev:live` harness. **It must never appear in a
1349
+ > test suite.** The free one is `createMockHost`, on `./testing`.
1350
+
1351
+ ### Why it has its own subpath
1352
+
1353
+ Until `0.55.0` this code was exported from `./testing`. The argument for moving
1354
+ it, in full, is that **a client which spends the caller's money should not be
1355
+ reachable through an import path named `testing`** — the import line is the one
1356
+ piece of context that travels with every call site, and `…/testing` actively
1357
+ asserts the opposite of what this module does. That is
1358
+ [#334](https://github.com/civitai/civitai-app-starters/issues/334)'s literal
1359
+ closing condition.
1360
+
1361
+ Two arguments that were made for this change and **do not hold** — recorded so
1362
+ they are not made again:
1363
+
1364
+ - **It does not shrink the install.** Measured: **+4,447 B**. See
1365
+ [What the host-simulation subpaths cost you](#what-the-host-simulation-subpaths-cost-you).
1366
+ - **It does not close a wrong-autocomplete hazard**, because there was none to
1367
+ close. `createMockHost(options: MockHostOptions = {})` is callable bare;
1368
+ `createLiveHost(options: LiveHostOptions)` takes a **required** argument whose
1369
+ `blockToken` is a **required** short-lived RS256 JWT that a human mints and
1370
+ pastes by hand. `createLiveHost()` and `createLiveHost({})` do not compile, so
1371
+ nobody reaches this module by picking the wrong completion. Earlier revisions
1372
+ of this file, of the changeset, and of #334 called the two signatures
1373
+ "near-identical"; none of them had read the signatures.
1374
+
1375
+ ### The whole surface
1376
+
1377
+ One value and one type. `test/subpathSurfaces.test.ts` pins the runtime export
1378
+ set, failing on growth and on shrinkage.
1379
+
1380
+ | Export | What it is |
1381
+ |---|---|
1382
+ | `createLiveHost` | 🔴 **Real backend, real Buzz.** Installs a host that proxies the block's `postMessage` traffic to civitai.com using a dev block token. Returns a handle; call `.install()` and keep the teardown, exactly like `createMockHost`. |
1383
+ | `LiveHostOptions` *(type)* | Options for the above. `blockToken` is required; everything else (`backendBaseUrl`, `viewer`, `theme`, `context`, `onOutbound`, …) has a default. |
1384
+
1385
+ ### In a `dev:live` harness
1386
+
1387
+ ```ts
1388
+ import { createLiveHost, type LiveHostOptions } from '@civitai/blocks-react/live';
1389
+
1390
+ // The token is a SHORT-LIVED dev block token pasted into the harness env, never
1391
+ // an API key: `POST /api/v1/blocks/dev-token`, ~4h, re-minted by hand.
1392
+ const options: LiveHostOptions = {
1393
+ blockToken: devBlockToken,
1394
+ theme: 'dark',
1395
+ };
1396
+
1397
+ const host = createLiveHost(options);
1398
+ const uninstall = host.install();
1399
+ ```
1400
+
1401
+ ### Stability of `/live`
1402
+
1403
+ The same terms as `./testing`: a normal subpath of a `0.x` package where a minor
1404
+ may break it, with the runtime symbol set pinned by
1405
+ `test/subpathSurfaces.test.ts` so it cannot change silently.
1406
+
1407
+ 🔴 One cost worth stating plainly: publishing and documenting this subpath makes
1408
+ [#334](https://github.com/civitai/civitai-app-starters/issues/334) **item 3** —
1409
+ getting the live-host code out of the tarball entirely — *harder*, not easier.
1410
+ `./live` is now a named public entry point, so removing it later is a breaking
1411
+ change on a surface consumers pin against, where before it was one export among
1412
+ many on a subpath nobody was told to rely on.
1413
+
1414
+ ## What the host-simulation subpaths cost you
1415
+
1416
+ Seven `dist/` modules — 266,790 B of JavaScript plus 77,338 B of `.d.ts` — are
1417
+ reachable only from `./testing` and `./live`, and from nothing under `.` or
1418
+ `./ui`. Measured by walking the built module graph:
1419
+
1420
+ ```text
1421
+ 118,590 dist/internal/mockHost.js ← ./testing (createMockHost)
1422
+ 86,688 dist/internal/liveHost.js ← ./live (createLiveHost)
1423
+ 29,508 dist/internal/pickerOverlay.js ← ./live (via liveHost)
1424
+ 15,351 dist/internal/catalog.js ← ./live (via pickerOverlay)
1425
+ 9,141 dist/testing.js
1426
+ 4,697 dist/internal/consent.js ← BOTH hosts import it
1427
+ 2,815 dist/live.js
1428
+ ```
1429
+
1430
+ **They ship in every install**, production dependency trees included. They are
1431
+ tree-shaken out of application *bundles* — no block ships a mock host to a
1432
+ browser — so this is `node_modules` weight, not bundle weight.
1433
+
1434
+ 🔴 **Splitting `createLiveHost` onto its own subpath removed none of this — it
1435
+ ADDS 4,447 B of code, and trimming the export list moved nothing either.**
1436
+ Measured with `pnpm pack` on both sides of the split, in a detached worktree so
1437
+ neither pack is contaminated by the other change in this release: **319 → 323
1438
+ entries, 1,590,099 B → 1,597,161 B uncompressed.** The only files that differ are
1439
+
1440
+ ```text
1441
+ +6,048 dist/live.* (new: .js 2,815, .d.ts 2,839, + maps)
1442
+ -1,692 dist/testing.* (the re-export and its docblock leaving)
1443
+ +91 package.json (the new exports-map key)
1444
+ +2,615 README.md (this section)
1445
+ ```
1446
+
1447
+ `liveHost.js`, `pickerOverlay.js` and `catalog.js` do not appear in that diff at
1448
+ all — they are byte-identical and still in the tarball. `files` is
1449
+ `["dist", "README.md"]` and `tsconfig` compiles all of `src/**/*`, so the
1450
+ `exports` map has no bearing whatsoever on tarball contents; it decides only what
1451
+ a consumer can *name*.
1452
+ **The `/live` split buys safety, not size.** Moving these bytes needs the code
1453
+ deleted or published as a second artifact; that is
1454
+ [#334](https://github.com/civitai/civitai-app-starters/issues/334) item 3, and it
1455
+ is not done here.
1456
+
1137
1457
  ## Examples
1138
1458
 
1139
1459
  Runnable, minimal blocks — one per feature, each with its own README:
@@ -14,9 +14,23 @@ export interface AppStorageListResult {
14
14
  export interface AppStorageQuota {
15
15
  usedBytes: number;
16
16
  rowCount: number;
17
- /** Host-enforced ceiling (bytes). Surface in UI so callers don't hard-code 50MB. */
17
+ /**
18
+ * Host-enforced byte ceiling for this (app, viewer). **Render THIS, never a
19
+ * hard-coded figure** — the ceiling moves, and a UI built on a literal goes
20
+ * quietly wrong rather than loudly wrong. Matches
21
+ * `APP_STORAGE_MAX_BYTES` from `@civitai/app-sdk/blocks` against the current
22
+ * host.
23
+ */
18
24
  limitBytes: number;
19
- /** Host-enforced row ceiling (~1M today). */
25
+ /**
26
+ * Host-enforced row ceiling for this (app, viewer). Same rule: render it.
27
+ * Matches `APP_STORAGE_MAX_ROWS`.
28
+ *
29
+ * 🔴 THIS IS USUALLY THE BINDING ONE. Rows are small; a block caching one
30
+ * modest record per item a viewer touches exhausts the row ceiling while
31
+ * still using a small fraction of `limitBytes`, so a byte-only "x of y used"
32
+ * readout will show plenty of headroom right up to the rejection.
33
+ */
20
34
  limitRows: number;
21
35
  }
22
36
  export interface UseAppStorage {
@@ -27,9 +41,11 @@ export interface UseAppStorage {
27
41
  */
28
42
  get<T = unknown>(key: string): Promise<T | null>;
29
43
  /**
30
- * Upsert a value. Resolves on host ack. Rejects with the host's
31
- * `error` string when the value exceeds 64KB, when the per-app 50MB
32
- * quota would be crossed, or when the viewer is anonymous.
44
+ * Upsert a value. Resolves on host ack. Rejects with the host's `error`
45
+ * string when the value exceeds `APP_STORAGE_MAX_VALUE_BYTES`, when the
46
+ * per-(app, viewer) byte or ROW ceiling would be crossed
47
+ * (`APP_STORAGE_MAX_BYTES` / `APP_STORAGE_MAX_ROWS`), or when the viewer is
48
+ * anonymous. All from `@civitai/app-sdk/blocks`.
33
49
  */
34
50
  set<T = unknown>(key: string, value: T): Promise<{
35
51
  ok: true;
@@ -54,8 +70,10 @@ export interface UseAppStorage {
54
70
  cursor?: string;
55
71
  }): Promise<AppStorageListResult>;
56
72
  /**
57
- * Diagnostic: current usage + the v0 ceilings. Build a "X of 50 MB used"
58
- * settings widget against this.
73
+ * Diagnostic: current usage + the host's ceilings. Build an "X of Y used"
74
+ * settings widget against this — taking **both** numbers from the reply, and
75
+ * showing ROWS as well as bytes (see {@link AppStorageQuota.limitRows} for
76
+ * why bytes alone mislead).
59
77
  */
60
78
  getQuota(): Promise<AppStorageQuota>;
61
79
  }
@@ -71,7 +89,20 @@ export interface UseAppStorage {
71
89
  * once the transport singleton is created, so it's safe to put in
72
90
  * dependency arrays of `useEffect` / `useMemo`.
73
91
  *
74
- * 64 KB per value, 50 MB + ~1M rows per app.
92
+ * 🔴 THE NAMESPACE AND THE BUDGET ARE SCOPED DIFFERENTLY. Keys are namespaced
93
+ * per (block instance, viewer) — the tuple above. The BYTE and ROW budgets
94
+ * (`APP_STORAGE_MAX_BYTES` / `APP_STORAGE_MAX_ROWS`, plus
95
+ * `APP_STORAGE_MAX_VALUE_BYTES` per value, all from `@civitai/app-sdk/blocks`)
96
+ * are enforced per (APP, viewer): every instance of the same app draws on ONE
97
+ * budget for that viewer.
98
+ *
99
+ * 🔴 THE CONSTANTS ARE A SNAPSHOT; `getQuota()` IS THE AUTHORITY. They are the
100
+ * ceilings as of the `@civitai/app-sdk` version you installed — a figure
101
+ * compiled into a published package is still a frozen figure, and the host can
102
+ * move a ceiling without your lockfile changing. Render `getQuota()`'s reply
103
+ * anywhere a viewer sees a number or a code path decides whether a write will
104
+ * fit; reach for a constant only where no reply is available (a test fixture, a
105
+ * design-time estimate), and re-check after an SDK bump.
75
106
  *
76
107
  * @example
77
108
  * const storage = useAppStorage();
@@ -1 +1 @@
1
- {"version":3,"file":"useAppStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useAppStorage.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACjD;;;;OAIG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnF;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7D;;;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,oBAAoB,CAAC,CAAC;IAClC;;;OAGG;IACH,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,IAAI,aAAa,CAiF7C"}
1
+ {"version":3,"file":"useAppStorage.d.ts","sourceRoot":"","sources":["../../src/hooks/useAppStorage.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACjD;;;;;;OAMG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnF;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7D;;;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,oBAAoB,CAAC,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,aAAa,IAAI,aAAa,CAiF7C"}
@@ -14,7 +14,20 @@ import { sendTypedRequest } from '../internal/transport.js';
14
14
  * once the transport singleton is created, so it's safe to put in
15
15
  * dependency arrays of `useEffect` / `useMemo`.
16
16
  *
17
- * 64 KB per value, 50 MB + ~1M rows per app.
17
+ * 🔴 THE NAMESPACE AND THE BUDGET ARE SCOPED DIFFERENTLY. Keys are namespaced
18
+ * per (block instance, viewer) — the tuple above. The BYTE and ROW budgets
19
+ * (`APP_STORAGE_MAX_BYTES` / `APP_STORAGE_MAX_ROWS`, plus
20
+ * `APP_STORAGE_MAX_VALUE_BYTES` per value, all from `@civitai/app-sdk/blocks`)
21
+ * are enforced per (APP, viewer): every instance of the same app draws on ONE
22
+ * budget for that viewer.
23
+ *
24
+ * 🔴 THE CONSTANTS ARE A SNAPSHOT; `getQuota()` IS THE AUTHORITY. They are the
25
+ * ceilings as of the `@civitai/app-sdk` version you installed — a figure
26
+ * compiled into a published package is still a frozen figure, and the host can
27
+ * move a ceiling without your lockfile changing. Render `getQuota()`'s reply
28
+ * anywhere a viewer sees a number or a code path decides whether a write will
29
+ * fit; reach for a constant only where no reply is available (a test fixture, a
30
+ * design-time estimate), and re-check after an SDK bump.
18
31
  *
19
32
  * @example
20
33
  * const storage = useAppStorage();
@@ -1 +1 @@
1
- {"version":3,"file":"useAppStorage.js","sourceRoot":"","sources":["../../src/hooks/useAppStorage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useAppStorage.js","sourceRoot":"","sources":["../../src/hooks/useAppStorage.ts"],"names":[],"mappings":"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"}
@@ -25,10 +25,14 @@ declare function useTransportSnapshot(): BlockSnapshot;
25
25
  * moves (today's behaviour).
26
26
  *
27
27
  * @example
28
+ * import { isSignedIn } from '@civitai/app-sdk/blocks';
28
29
  * const { ready, context, viewer, theme, settings } = useBlockContext();
29
30
  * if (!ready) return <div>Loading…</div>;
30
31
  * // Set data-theme on YOUR root — the host can't reach into the iframe (gotcha #60).
31
- * return <div data-theme={theme}>Hi {viewer?.username ?? 'anon'}</div>;
32
+ * // Sign-in gate: call `isSignedIn`. NOT `viewer?.username` — an identity read
33
+ * // standing in for a presence check, on a field that is `@deprecated` and
34
+ * // scheduled for removal. This snippet used to do exactly that.
35
+ * return <div data-theme={theme}>{isSignedIn(viewer) ? 'Hi there' : 'Hi anon'}</div>;
32
36
  */
33
37
  export declare function useBlockContext(): Pick<BlockSnapshot, 'ready' | 'renderMode' | 'context' | 'token' | 'settings' | 'viewer' | 'theme' | 'blockId' | 'blockInstanceId' | 'appId'>;
34
38
  /** Re-exported so other hooks in this package can share the subscription. */
@@ -1 +1 @@
1
- {"version":3,"file":"useBlockContext.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockContext.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;GAGG;AACH,iBAAS,oBAAoB,IAAI,aAAa,CAS7C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,IAAI,IAAI,CACrC,aAAa,EACX,OAAO,GACP,YAAY,GACZ,SAAS,GACT,OAAO,GACP,UAAU,GACV,QAAQ,GACR,OAAO,GACP,SAAS,GACT,iBAAiB,GACjB,OAAO,CACV,CAcA;AAED,6EAA6E;AAC7E,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
1
+ {"version":3,"file":"useBlockContext.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockContext.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;GAGG;AACH,iBAAS,oBAAoB,IAAI,aAAa,CAS7C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,eAAe,IAAI,IAAI,CACrC,aAAa,EACX,OAAO,GACP,YAAY,GACZ,SAAS,GACT,OAAO,GACP,UAAU,GACV,QAAQ,GACR,OAAO,GACP,SAAS,GACT,iBAAiB,GACjB,OAAO,CACV,CAcA;AAED,6EAA6E;AAC7E,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
@@ -32,10 +32,14 @@ function useTransportSnapshot() {
32
32
  * moves (today's behaviour).
33
33
  *
34
34
  * @example
35
+ * import { isSignedIn } from '@civitai/app-sdk/blocks';
35
36
  * const { ready, context, viewer, theme, settings } = useBlockContext();
36
37
  * if (!ready) return <div>Loading…</div>;
37
38
  * // Set data-theme on YOUR root — the host can't reach into the iframe (gotcha #60).
38
- * return <div data-theme={theme}>Hi {viewer?.username ?? 'anon'}</div>;
39
+ * // Sign-in gate: call `isSignedIn`. NOT `viewer?.username` — an identity read
40
+ * // standing in for a presence check, on a field that is `@deprecated` and
41
+ * // scheduled for removal. This snippet used to do exactly that.
42
+ * return <div data-theme={theme}>{isSignedIn(viewer) ? 'Hi there' : 'Hi anon'}</div>;
39
43
  */
40
44
  export function useBlockContext() {
41
45
  const snap = useTransportSnapshot();
@@ -1 +1 @@
1
- {"version":3,"file":"useBlockContext.js","sourceRoot":"","sources":["../../src/hooks/useBlockContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD;;;GAGG;AACH,SAAS,oBAAoB;IAC3B,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,OAAO,oBAAoB,CACzB,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,EAC/B,GAAG,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE;IAC7B,iEAAiE;IACjE,mEAAmE;IACnE,GAAG,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,CAC9B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,eAAe;IAa7B,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;IACpC,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
1
+ {"version":3,"file":"useBlockContext.js","sourceRoot":"","sources":["../../src/hooks/useBlockContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD;;;GAGG;AACH,SAAS,oBAAoB;IAC3B,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,OAAO,oBAAoB,CACzB,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,EAC/B,GAAG,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE;IAC7B,iEAAiE;IACjE,mEAAmE;IACnE,GAAG,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,CAC9B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,eAAe;IAa7B,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;IACpC,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
@@ -47,7 +47,9 @@
47
47
  * tRPC procedures (publicProcedure + verifyBlockToken, FLAT `{ blockToken, … }`
48
48
  * input). Reads need the `apps:storage:read` scope, writes `apps:storage:write`
49
49
  * — the dev token already carries whatever the local manifest declared, and the
50
- * server enforces. Real per-(block_instance, user) KV, real 64KB/50MB quotas.
50
+ * server enforces. Real KV — keys namespaced per (block_instance, user), byte
51
+ * and row budgets enforced per (app, user) — with the REAL ceilings, so a
52
+ * write this host accepts is one production would accept.
51
53
  *
52
54
  * SET_USER_CHECKPOINT (Phase 4): FORWARDED (faithful) to the block-token
53
55
  * `blocks.updateUserSettings` mutation — never fabricated. The default page
@@ -1 +1 @@
1
- {"version":3,"file":"liveHost.d.ts","sourceRoot":"","sources":["../../src/internal/liveHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,WAAW,EAChB,KAAK,KAAK,EACV,KAAK,UAAU,EAGhB,MAAM,yBAAyB,CAAC;AAIjC,OAAO,KAAK,EAAE,QAAQ,EAAyC,MAAM,eAAe,CAAC;AACrF,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAK5B;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,wEAAwE;IACxE,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,UAAU,CAAC;IACpC;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACvD;AAQD;;;;;GAKG;AACH,UAAU,wBAAwB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,wBAAwB,CAgB/E;AAyHD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CAu9CjE"}
1
+ {"version":3,"file":"liveHost.d.ts","sourceRoot":"","sources":["../../src/internal/liveHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAEH,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,WAAW,EAChB,KAAK,KAAK,EACV,KAAK,UAAU,EAGhB,MAAM,yBAAyB,CAAC;AAIjC,OAAO,KAAK,EAAE,QAAQ,EAAyC,MAAM,eAAe,CAAC;AACrF,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,oBAAoB,CAAC;AAK5B;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,wEAAwE;IACxE,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,UAAU,CAAC;IACpC;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACvD;AAQD;;;;;GAKG;AACH,UAAU,wBAAwB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,wBAAwB,CAgB/E;AAyHD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CAw9CjE"}
@@ -47,7 +47,9 @@
47
47
  * tRPC procedures (publicProcedure + verifyBlockToken, FLAT `{ blockToken, … }`
48
48
  * input). Reads need the `apps:storage:read` scope, writes `apps:storage:write`
49
49
  * — the dev token already carries whatever the local manifest declared, and the
50
- * server enforces. Real per-(block_instance, user) KV, real 64KB/50MB quotas.
50
+ * server enforces. Real KV — keys namespaced per (block_instance, user), byte
51
+ * and row budgets enforced per (app, user) — with the REAL ceilings, so a
52
+ * write this host accepts is one production would accept.
51
53
  *
52
54
  * SET_USER_CHECKPOINT (Phase 4): FORWARDED (faithful) to the block-token
53
55
  * `blocks.updateUserSettings` mutation — never fabricated. The default page
@@ -396,10 +398,9 @@ export function createLiveHost(options) {
396
398
  return anonFallbackViewer();
397
399
  // `/api/v1/blocks/me` is the AUTHORITATIVE self-read and does carry
398
400
  // `status` — but this builds `BLOCK_INIT.viewer`, which the real host
399
- // projects down to `{ id, username }` today (civitai/civitai `main`'s
400
- // `projectBlockInitViewer`), and to `{ id, username, signedIn }` once
401
- // civitai/civitai#3707 (OPEN, unmerged) lands. Never moderation state,
402
- // in either version (civitai #2521). Forwarding `me.status` here would
401
+ // projects down to exactly `{ id, username, signedIn }` (civitai/civitai
402
+ // `main`'s `withSignedInFlag`). Never moderation state (civitai #2521).
403
+ // Forwarding `me.status` here would
403
404
  // make the live dev host more generous than production. A block that
404
405
  // wants `status` must ask for it via `GET_VIEWER` / `useViewer()`, which
405
406
  // is exactly the scope-gated path this release steers authors to.
@@ -1012,7 +1013,9 @@ export function createLiveHost(options) {
1012
1013
  }
1013
1014
  case 'APP_STORAGE_SET': {
1014
1015
  // Mutation apps.storage.set {blockToken, key, value} (POST). The
1015
- // server enforces apps:storage:write + the 64KB/50MB quotas.
1016
+ // server enforces apps:storage:write + the per-value cap and both
1017
+ // per-(app, viewer) budgets — no simulation here, so nothing to
1018
+ // keep in sync.
1016
1019
  const key = typed.payload?.key ?? '';
1017
1020
  const value = typed.payload?.value;
1018
1021
  void callTrpcData('apps.storage.set', { blockToken: rawToken, key, value }, 'POST').then((r) => {
@@ -1461,23 +1464,21 @@ export function createLiveHost(options) {
1461
1464
  /**
1462
1465
  * A minimal anon-ish viewer used when `/api/v1/blocks/me` can't be reached.
1463
1466
  *
1464
- * Carries EXACTLY `{ id, username, signedIn }` — the key set the BLOCK_INIT
1465
- * contract is moving to. The two halves have different provenance:
1467
+ * Carries EXACTLY `{ id, username, signedIn }` — byte-for-byte the key set the
1468
+ * real host puts on the wire:
1466
1469
  *
1467
- * - NO `status` is production TODAY. civitai/civitai `main`'s
1468
- * `projectBlockInitViewer` builds `{ id, username }`, pinned as exactly
1469
- * `['id', 'username']` by `__tests__/projectBlockInit.test.ts`. The platform
1470
- * withholds the viewer's moderation state from third-party iframes (civitai
1471
- * #2521), so a dev host that sends it invites a block to read a field
1472
- * production never provides.
1473
- * - `signedIn` is NOT production yet. It appears zero times under
1474
- * `src/components/AppBlocks/` on `main`; it arrives with civitai/civitai#3707
1475
- * (OPEN, unmerged), which also moves the host's pinned key set to
1476
- * `['id', 'signedIn', 'username']`. Emitted here so the field is exercisable
1477
- * locally ahead of the host — `viewer !== null` is still the gate to SHIP.
1470
+ * - NO `status`. The platform withholds the viewer's moderation state from
1471
+ * third-party iframes (civitai #2521), so a dev host that sent it would
1472
+ * invite a block to read a field production never provides.
1473
+ * - WITH `signedIn: true`. civitai/civitai `main`'s `withSignedInFlag`
1474
+ * (`src/components/AppBlocks/projectBlockInit.ts`) stamps it on every
1475
+ * present viewer from BOTH host surfaces, and that repo's
1476
+ * `__tests__/projectBlockInit.test.ts` pins the viewer key set as exactly
1477
+ * `['id', 'signedIn', 'username']`.
1478
1478
  *
1479
- * See {@link DEFAULT_VIEWER} in `mockHost` for the same note and for what to
1480
- * unwind if #3707 is abandoned.
1479
+ * 🔴 THE PROPERTY THIS FENCE HOLDS: the dev hosts must not be more generous
1480
+ * than the host they imitate — this default MATCHES the host, it does not run
1481
+ * ahead of it. See `DEFAULT_VIEWER` in `mockHost` for the same note.
1481
1482
  */
1482
1483
  function anonFallbackViewer() {
1483
1484
  return { id: 0, username: 'dev-live', signedIn: true };