@civitai/blocks-react 0.53.0 → 0.54.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 +78 -8
- package/dist/hooks/useAppStorage.d.ts +39 -8
- package/dist/hooks/useAppStorage.d.ts.map +1 -1
- package/dist/hooks/useAppStorage.js +14 -1
- package/dist/hooks/useAppStorage.js.map +1 -1
- package/dist/hooks/useBlockContext.d.ts +5 -1
- package/dist/hooks/useBlockContext.d.ts.map +1 -1
- package/dist/hooks/useBlockContext.js +5 -1
- package/dist/hooks/useBlockContext.js.map +1 -1
- package/dist/hooks/useBlockResize.d.ts +26 -0
- package/dist/hooks/useBlockResize.d.ts.map +1 -1
- package/dist/hooks/useBlockResize.js +52 -5
- package/dist/hooks/useBlockResize.js.map +1 -1
- package/dist/internal/liveHost.d.ts +3 -1
- package/dist/internal/liveHost.d.ts.map +1 -1
- package/dist/internal/liveHost.js +22 -21
- package/dist/internal/liveHost.js.map +1 -1
- package/dist/internal/mockHost.d.ts +37 -5
- package/dist/internal/mockHost.d.ts.map +1 -1
- package/dist/internal/mockHost.js +80 -34
- package/dist/internal/mockHost.js.map +1 -1
- package/dist/internal/transport.d.ts +4 -3
- package/dist/internal/transport.d.ts.map +1 -1
- package/dist/internal/transport.js.map +1 -1
- package/dist/internal/validate.d.ts.map +1 -1
- package/dist/internal/validate.js +22 -17
- package/dist/internal/validate.js.map +1 -1
- package/package.json +69 -38
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();
|
|
@@ -35,7 +35,9 @@ export function App() {
|
|
|
35
35
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
36
36
|
useBlockResize(rootRef); // host fits the iframe to content
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// No ref on the pre-init skeleton — useBlockResize observes the real root
|
|
39
|
+
// whenever it mounts, including on a later render.
|
|
40
|
+
if (!ready) return <div>Loading…</div>;
|
|
39
41
|
// `context` is a union keyed on slotId — narrow with the guard, not a cast.
|
|
40
42
|
if (!isModelSlotContext(context)) return <div ref={rootRef}>Wrong slot.</div>;
|
|
41
43
|
|
|
@@ -43,7 +45,8 @@ export function App() {
|
|
|
43
45
|
// GOTCHA #60: set data-theme on YOUR OWN root — the host can't reach into
|
|
44
46
|
// the iframe to set it. Without this any [data-theme="dark"] CSS is dormant.
|
|
45
47
|
<div ref={rootRef} data-theme={theme}>
|
|
46
|
-
|
|
48
|
+
{/* Sign-in gate: call `isSignedIn`, never an identity read. */}
|
|
49
|
+
<p>Block for model {context.modelName} ({isSignedIn(viewer) ? 'signed in' : 'anon'})</p>
|
|
47
50
|
{/* `/ui` Button — themed by the data-theme above; `loading` disables + shows a spinner */}
|
|
48
51
|
<Button
|
|
49
52
|
loading={status === 'submitting' || status === 'polling'}
|
|
@@ -111,7 +114,14 @@ const { ready, context, viewer, theme, settings, blockId, blockInstanceId, appId
|
|
|
111
114
|
|
|
112
115
|
- `context` — `BlockContext` (`{ slotId, … }`); narrow to `ModelSlotContext` for
|
|
113
116
|
model-page slots.
|
|
114
|
-
- `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.
|
|
115
125
|
- `theme` — `'light' | 'dark'`. **Set `data-theme={theme}` on your root** (gotcha #60).
|
|
116
126
|
LIVE: it starts at the `BLOCK_INIT` value and then tracks the host's
|
|
117
127
|
`THEME_CHANGE` push when the viewer toggles dark mode mid-session — see
|
|
@@ -153,6 +163,12 @@ const rootRef = useRef<HTMLDivElement>(null);
|
|
|
153
163
|
useBlockResize(rootRef);
|
|
154
164
|
```
|
|
155
165
|
|
|
166
|
+
**The element may mount on a later render, and that is the normal case** — a
|
|
167
|
+
block renders a skeleton until `BLOCK_INIT` lands. The hook keys on the observed
|
|
168
|
+
*element*, so you do **not** need to pin the same `ref` to every branch of a
|
|
169
|
+
loading/ready conditional to keep the host resizing. Put it on the root you
|
|
170
|
+
actually want measured, in whichever branch renders it.
|
|
171
|
+
|
|
156
172
|
> Also set `iframe.minHeight` in your manifest to the block's *real* rendered
|
|
157
173
|
> height — a too-small minHeight makes the iframe seed short and grow-jump on
|
|
158
174
|
> `BLOCK_READY` (CLS). Measure it in the dev harness (gotcha #53).
|
|
@@ -676,18 +692,72 @@ async function onCancel(id: string) {
|
|
|
676
692
|
|
|
677
693
|
### `useAppStorage()`
|
|
678
694
|
|
|
679
|
-
|
|
680
|
-
|
|
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.
|
|
681
698
|
|
|
682
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
|
+
|
|
683
706
|
const storage = useAppStorage();
|
|
684
|
-
await storage.set('key', { any: 'json' }); //
|
|
707
|
+
await storage.set('key', { any: 'json' }); // rejects over ANY of the three ceilings
|
|
685
708
|
const v = await storage.get<MyShape>('key'); // null if unset / anon
|
|
686
709
|
await storage.delete('key'); // idempotent
|
|
687
710
|
const { keys } = await storage.list({ prefix: 'note-' });
|
|
688
711
|
const quota = await storage.getQuota(); // { usedBytes, rowCount, limitBytes, limitRows }
|
|
689
712
|
```
|
|
690
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
|
+
|
|
691
761
|
### `useSharedStorage()`
|
|
692
762
|
|
|
693
763
|
App-scoped, append-only, community-votable SHARED datastore (every viewer sees
|
|
@@ -1039,7 +1109,7 @@ import {
|
|
|
1039
1109
|
export function App() {
|
|
1040
1110
|
const { ready, theme } = useBlockContext();
|
|
1041
1111
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
1042
|
-
if (!ready) return <div
|
|
1112
|
+
if (!ready) return <div>Loading…</div>;
|
|
1043
1113
|
|
|
1044
1114
|
return (
|
|
1045
1115
|
// GOTCHA #60 — theme your OWN root; that's what the pack reads.
|
|
@@ -14,9 +14,23 @@ export interface AppStorageListResult {
|
|
|
14
14
|
export interface AppStorageQuota {
|
|
15
15
|
usedBytes: number;
|
|
16
16
|
rowCount: number;
|
|
17
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
32
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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"}
|
|
@@ -9,11 +9,37 @@ import { type RefObject } from 'react';
|
|
|
9
9
|
* - Inline path: `InlineTransport.sendMessage` is a no-op (the host DOM
|
|
10
10
|
* reflows naturally), so the observer fires but no message goes out.
|
|
11
11
|
*
|
|
12
|
+
* 🔴 THE ELEMENT MAY MOUNT ON A LATER RENDER, AND THAT IS THE NORMAL CASE.
|
|
13
|
+
* Every block renders a skeleton until `BLOCK_INIT` lands, so on the first
|
|
14
|
+
* render there is nothing to observe. This hook therefore keys on the OBSERVED
|
|
15
|
+
* ELEMENT, not on the ref wrapper's identity — the same reasoning written down
|
|
16
|
+
* at `useBlockBreakpoint.ts`'s effect, with one mechanical difference that
|
|
17
|
+
* matters:
|
|
18
|
+
*
|
|
19
|
+
* `useBlockBreakpoint` can compare `ref.current` read DURING RENDER, because
|
|
20
|
+
* it re-renders its own caller and so always gets another render in which to
|
|
21
|
+
* notice. This hook re-renders nobody. React attaches a ref during COMMIT,
|
|
22
|
+
* i.e. AFTER the render that mounts it, so a `[ref.current]` dependency read
|
|
23
|
+
* during render is a render behind and — with no further render coming —
|
|
24
|
+
* never catches up. Measured: with `[ref.current]` as the dependency, a
|
|
25
|
+
* component that mounts its root on the second render still observes
|
|
26
|
+
* nothing.
|
|
27
|
+
*
|
|
28
|
+
* So the effect runs on every render (no dependency array) and does its own
|
|
29
|
+
* identity check against the element it is already observing. The check is a
|
|
30
|
+
* reference compare; the observer is torn down and rebuilt only when the
|
|
31
|
+
* element actually changes.
|
|
32
|
+
*
|
|
33
|
+
* Because of this, a block does NOT need to pin the same `ref` to every branch
|
|
34
|
+
* of a loading/ready conditional to keep the host resizing. That workaround was
|
|
35
|
+
* load-bearing before this fix and is not any more.
|
|
36
|
+
*
|
|
12
37
|
* @param ref - Ref to the block's root DOM element to observe.
|
|
13
38
|
*
|
|
14
39
|
* @example
|
|
15
40
|
* const rootRef = useRef<HTMLDivElement>(null);
|
|
16
41
|
* useBlockResize(rootRef); // host fits the iframe to content
|
|
42
|
+
* if (!ready) return <div>Loading…</div>; // no ref needed on this branch
|
|
17
43
|
* return <div ref={rootRef}>…</div>;
|
|
18
44
|
*/
|
|
19
45
|
export declare function useBlockResize(ref: RefObject<HTMLElement | null>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBlockResize.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockResize.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"useBlockResize.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockResize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,CAyCvE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { getTransport } from '../internal/singleton.js';
|
|
3
3
|
/**
|
|
4
4
|
* Observes the referenced element's height and asks the host to resize on
|
|
@@ -10,20 +10,67 @@ import { getTransport } from '../internal/singleton.js';
|
|
|
10
10
|
* - Inline path: `InlineTransport.sendMessage` is a no-op (the host DOM
|
|
11
11
|
* reflows naturally), so the observer fires but no message goes out.
|
|
12
12
|
*
|
|
13
|
+
* 🔴 THE ELEMENT MAY MOUNT ON A LATER RENDER, AND THAT IS THE NORMAL CASE.
|
|
14
|
+
* Every block renders a skeleton until `BLOCK_INIT` lands, so on the first
|
|
15
|
+
* render there is nothing to observe. This hook therefore keys on the OBSERVED
|
|
16
|
+
* ELEMENT, not on the ref wrapper's identity — the same reasoning written down
|
|
17
|
+
* at `useBlockBreakpoint.ts`'s effect, with one mechanical difference that
|
|
18
|
+
* matters:
|
|
19
|
+
*
|
|
20
|
+
* `useBlockBreakpoint` can compare `ref.current` read DURING RENDER, because
|
|
21
|
+
* it re-renders its own caller and so always gets another render in which to
|
|
22
|
+
* notice. This hook re-renders nobody. React attaches a ref during COMMIT,
|
|
23
|
+
* i.e. AFTER the render that mounts it, so a `[ref.current]` dependency read
|
|
24
|
+
* during render is a render behind and — with no further render coming —
|
|
25
|
+
* never catches up. Measured: with `[ref.current]` as the dependency, a
|
|
26
|
+
* component that mounts its root on the second render still observes
|
|
27
|
+
* nothing.
|
|
28
|
+
*
|
|
29
|
+
* So the effect runs on every render (no dependency array) and does its own
|
|
30
|
+
* identity check against the element it is already observing. The check is a
|
|
31
|
+
* reference compare; the observer is torn down and rebuilt only when the
|
|
32
|
+
* element actually changes.
|
|
33
|
+
*
|
|
34
|
+
* Because of this, a block does NOT need to pin the same `ref` to every branch
|
|
35
|
+
* of a loading/ready conditional to keep the host resizing. That workaround was
|
|
36
|
+
* load-bearing before this fix and is not any more.
|
|
37
|
+
*
|
|
13
38
|
* @param ref - Ref to the block's root DOM element to observe.
|
|
14
39
|
*
|
|
15
40
|
* @example
|
|
16
41
|
* const rootRef = useRef<HTMLDivElement>(null);
|
|
17
42
|
* useBlockResize(rootRef); // host fits the iframe to content
|
|
43
|
+
* if (!ready) return <div>Loading…</div>; // no ref needed on this branch
|
|
18
44
|
* return <div ref={rootRef}>…</div>;
|
|
19
45
|
*/
|
|
20
46
|
export function useBlockResize(ref) {
|
|
47
|
+
/** The element the live observer is watching. `null` = watching nothing. */
|
|
48
|
+
const observedRef = useRef(null);
|
|
49
|
+
const observerRef = useRef(null);
|
|
50
|
+
// Unmount-only teardown. Declared FIRST so that on a remount (React
|
|
51
|
+
// StrictMode's deliberate double-invoke) its cleanup — which clears
|
|
52
|
+
// `observedRef` — runs before the observe effect's setup re-runs and finds a
|
|
53
|
+
// clean slate. React runs every cleanup before any setup.
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
return () => {
|
|
56
|
+
observerRef.current?.disconnect();
|
|
57
|
+
observerRef.current = null;
|
|
58
|
+
observedRef.current = null;
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
// No dependency array on purpose — see the block comment above. The identity
|
|
62
|
+
// check below, not a dependency list, is what makes this cheap.
|
|
21
63
|
useEffect(() => {
|
|
64
|
+
if (typeof ResizeObserver === 'undefined')
|
|
65
|
+
return;
|
|
22
66
|
const el = ref.current;
|
|
67
|
+
if (el === observedRef.current)
|
|
68
|
+
return; // already observing exactly this
|
|
69
|
+
observerRef.current?.disconnect();
|
|
70
|
+
observerRef.current = null;
|
|
71
|
+
observedRef.current = el;
|
|
23
72
|
if (!el)
|
|
24
73
|
return;
|
|
25
|
-
if (typeof ResizeObserver === 'undefined')
|
|
26
|
-
return;
|
|
27
74
|
const transport = getTransport();
|
|
28
75
|
let lastHeight = -1;
|
|
29
76
|
const observer = new ResizeObserver((entries) => {
|
|
@@ -34,7 +81,7 @@ export function useBlockResize(ref) {
|
|
|
34
81
|
transport.sendMessage({ type: 'RESIZE_IFRAME', payload: { height } });
|
|
35
82
|
});
|
|
36
83
|
observer.observe(el);
|
|
37
|
-
|
|
38
|
-
}
|
|
84
|
+
observerRef.current = observer;
|
|
85
|
+
});
|
|
39
86
|
}
|
|
40
87
|
//# sourceMappingURL=useBlockResize.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBlockResize.js","sourceRoot":"","sources":["../../src/hooks/useBlockResize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useBlockResize.js","sourceRoot":"","sources":["../../src/hooks/useBlockResize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,UAAU,cAAc,CAAC,GAAkC;IAC/D,4EAA4E;IAC5E,MAAM,WAAW,GAAG,MAAM,CAAqB,IAAI,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAExD,oEAAoE;IACpE,oEAAoE;IACpE,6EAA6E;IAC7E,0DAA0D;IAC1D,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;YAClC,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;YAC3B,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC7B,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,6EAA6E;IAC7E,gEAAgE;IAChE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,cAAc,KAAK,WAAW;YAAE,OAAO;QAElD,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC;QACvB,IAAI,EAAE,KAAK,WAAW,CAAC,OAAO;YAAE,OAAO,CAAC,iCAAiC;QAEzE,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;QAClC,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC3B,WAAW,CAAC,OAAO,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,EAAE;YAAE,OAAO;QAEhB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;QACjC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;QACpB,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE;YAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,MAAM,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC;YAC5E,IAAI,MAAM,KAAK,UAAU;gBAAE,OAAO;YAClC,UAAU,GAAG,MAAM,CAAC;YACpB,SAAS,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACrB,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,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
|
|
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
|
|
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
|
|
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 }`
|
|
400
|
-
// `
|
|
401
|
-
//
|
|
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
|
|
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
|
|
1465
|
-
*
|
|
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
|
|
1468
|
-
*
|
|
1469
|
-
*
|
|
1470
|
-
*
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
* `
|
|
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
|
-
*
|
|
1480
|
-
*
|
|
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 };
|