react-native-webrtc-kaleidoscope 2.2.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +30 -0
  2. package/dist/src/components/preset-control-panel/preset-control-panel.d.ts +9 -1
  3. package/dist/src/components/preset-control-panel/preset-control-panel.d.ts.map +1 -1
  4. package/dist/src/components/preset-control-panel/preset-control-panel.js +7 -3
  5. package/dist/src/components/preset-control-panel/preset-control-panel.js.map +1 -1
  6. package/dist/src/kaleidoscope/controls.d.ts.map +1 -1
  7. package/dist/src/kaleidoscope/controls.js +6 -3
  8. package/dist/src/kaleidoscope/controls.js.map +1 -1
  9. package/dist/src/kaleidoscope/shader-to-spec.d.ts +15 -1
  10. package/dist/src/kaleidoscope/shader-to-spec.d.ts.map +1 -1
  11. package/dist/src/kaleidoscope/shader-to-spec.js +23 -4
  12. package/dist/src/kaleidoscope/shader-to-spec.js.map +1 -1
  13. package/dist/src/kaleidoscope/types.d.ts +3 -1
  14. package/dist/src/kaleidoscope/types.d.ts.map +1 -1
  15. package/dist/src/kaleidoscope/types.js.map +1 -1
  16. package/dist/src/persistence/async-storage-store.d.ts +3 -0
  17. package/dist/src/persistence/async-storage-store.d.ts.map +1 -0
  18. package/dist/src/persistence/async-storage-store.js +26 -0
  19. package/dist/src/persistence/async-storage-store.js.map +1 -0
  20. package/dist/src/persistence/index.d.ts +4 -0
  21. package/dist/src/persistence/index.d.ts.map +1 -0
  22. package/dist/src/persistence/index.js +22 -0
  23. package/dist/src/persistence/index.js.map +1 -0
  24. package/dist/src/persistence/provider.d.ts +38 -0
  25. package/dist/src/persistence/provider.d.ts.map +1 -0
  26. package/dist/src/persistence/provider.js +96 -0
  27. package/dist/src/persistence/provider.js.map +1 -0
  28. package/dist/src/persistence/state.d.ts +54 -0
  29. package/dist/src/persistence/state.d.ts.map +1 -0
  30. package/dist/src/persistence/state.js +126 -0
  31. package/dist/src/persistence/state.js.map +1 -0
  32. package/package.json +14 -2
  33. package/src/components/preset-control-panel/preset-control-panel.tsx +15 -2
  34. package/src/kaleidoscope/controls.ts +6 -3
  35. package/src/kaleidoscope/shader-to-spec.ts +32 -5
  36. package/src/kaleidoscope/types.ts +3 -1
  37. package/src/persistence/async-storage-store.ts +33 -0
  38. package/src/persistence/index.ts +28 -0
  39. package/src/persistence/provider.tsx +165 -0
  40. package/src/persistence/state.ts +167 -0
package/README.md CHANGED
@@ -245,6 +245,36 @@ Like the picker, the editor is controlled and presentational: it emits patches a
245
245
 
246
246
  Live per-layer tuning runs on web today; on native the editor renders but the live per-layer uniform channel is in progress. Mask and transform are live on every platform.
247
247
 
248
+ ### Persistence (the selection that survives a reload)
249
+
250
+ `react-native-webrtc-kaleidoscope/persistence` ships a provider + hook that keep the person's selection across launches: the last applied preset id, the per-layer uniform patches they dialed in through the control panels (kept per preset, so tweaks to several presets all survive), and the mask edge.
251
+
252
+ ```tsx
253
+ // App root:
254
+ import { KaleidoscopeStateProvider } from 'react-native-webrtc-kaleidoscope/persistence';
255
+ import { presets } from './kaleidoscope.preset-book';
256
+
257
+ <KaleidoscopeStateProvider presets={presets}>
258
+ <App />
259
+ </KaleidoscopeStateProvider>;
260
+
261
+ // In the screen that binds the track:
262
+ import { useKaleidoscopeState } from 'react-native-webrtc-kaleidoscope/persistence';
263
+
264
+ const { hydrated, presetId, mask, setPreset, setMask, setPatch, patchesFor, reset } =
265
+ useKaleidoscopeState<typeof presets>();
266
+
267
+ useEffect(() => {
268
+ if (!hydrated || !controls) return; // wait: don't flash the default over the restored preset
269
+ if (presetId) controls.kaleidoscope(presetId, patchesFor(presetId));
270
+ else controls.kaleidoscope(null);
271
+ }, [hydrated, controls, presetId]);
272
+ ```
273
+
274
+ Route the picker's `onSelect` into `setPreset`, the editor's `onPatch` into `setPatch(presetId, patch)` (and apply the live patch as usual), and the mask panel into `setMask`; every write persists. Pass the editor `patches={patches[presetId]}` so restored tweaks appear in the sliders, and mount it after `hydrated` (the forms seed at mount). A stored preset that no longer exists in your book reads as "none" instead of crashing the picker.
275
+
276
+ The default store is [`@react-native-async-storage/async-storage`](https://github.com/react-native-async-storage/async-storage) (an optional peer; install it alongside the library when you use this subpath; on web it is localStorage-backed). To back it with something else (MMKV, a server), pass any `{ load, save }` pair as the `store` prop; the stored shape is versioned and parses tolerantly, so a malformed payload reads as empty rather than throwing.
277
+
248
278
  ## Worlds
249
279
 
250
280
  Packaged composites: a multi-layer stack (a generative shader or a cut-out image, the masked person on top), imported and spread into your book (e.g. `import { wizardTower } from 'react-native-webrtc-kaleidoscope/composites/wizard-tower'`). They carry their own `taxonomy: ['Worlds', <group>]`, so the menu groups them under the Worlds tab.
@@ -6,7 +6,15 @@ export type PresetControlPanelProps<P extends KaleidoscopePresetBook> = {
6
6
  readonly value: (keyof P & string) | null;
7
7
  /** Routed to the host, which applies it via `kaleidoscope(value, [patch])`. */
8
8
  readonly onPatch: KaleidoscopeControls['onPatch'];
9
+ /**
10
+ * Per-layer uniform overrides (e.g. a persisted selection's stored patches)
11
+ * merged over the preset's baked uniforms when the forms seed. Seed-time
12
+ * only: the forms re-seed on a preset switch, not when this prop changes
13
+ * mid-mount, so hosts restoring persisted patches should mount the panel
14
+ * after hydration.
15
+ */
16
+ readonly patches?: KaleidoscopeControls['uniforms'];
9
17
  readonly disabled?: boolean;
10
18
  };
11
- export declare function PresetControlPanel<P extends KaleidoscopePresetBook>({ presets, value, onPatch, disabled, }: PresetControlPanelProps<P>): ReactElement | null;
19
+ export declare function PresetControlPanel<P extends KaleidoscopePresetBook>({ presets, value, onPatch, patches, disabled, }: PresetControlPanelProps<P>): ReactElement | null;
12
20
  //# sourceMappingURL=preset-control-panel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preset-control-panel.d.ts","sourceRoot":"","sources":["../../../../src/components/preset-control-panel/preset-control-panel.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,sCAAsC,CAAC;AAG9C,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,sBAAsB,IAAI;IACtE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;IAC1C,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,sBAAsB,EAAE,EACnE,OAAO,EACP,KAAK,EACL,OAAO,EACP,QAAgB,GACjB,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,IAAI,CAoBlD"}
1
+ {"version":3,"file":"preset-control-panel.d.ts","sourceRoot":"","sources":["../../../../src/components/preset-control-panel/preset-control-panel.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,sCAAsC,CAAC;AAG9C,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,sBAAsB,IAAI;IACtE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;IAC1C,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAClD;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,sBAAsB,EAAE,EACnE,OAAO,EACP,KAAK,EACL,OAAO,EACP,OAAO,EACP,QAAgB,GACjB,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAAG,YAAY,GAAG,IAAI,CAwBlD"}
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PresetControlPanel = PresetControlPanel;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const scope_1 = require("../form/scope");
6
- function PresetControlPanel({ presets, value, onPatch, disabled = false, }) {
6
+ function PresetControlPanel({ presets, value, onPatch, patches, disabled = false, }) {
7
7
  if (value === null)
8
8
  return null;
9
9
  const preset = presets[value];
@@ -11,11 +11,15 @@ function PresetControlPanel({ presets, value, onPatch, disabled = false, }) {
11
11
  if (!Controls)
12
12
  return null;
13
13
  // Per-layer baked uniforms keyed by id, for the controls component to seed each
14
- // layer's ControlForm. Only tunable layers carry uniforms.
14
+ // layer's ControlForm. Only tunable layers carry uniforms; stored overrides
15
+ // merge over the baked values so restored tweaks appear in the forms.
15
16
  const uniforms = {};
16
17
  for (const layer of preset.layers) {
17
18
  if ('uniforms' in layer) {
18
- uniforms[layer.id] = { ...layer.uniforms };
19
+ uniforms[layer.id] = {
20
+ ...layer.uniforms,
21
+ ...patches?.[layer.id],
22
+ };
19
23
  }
20
24
  }
21
25
  return ((0, jsx_runtime_1.jsx)(scope_1.ControlScopeContext.Provider, { value: value, children: (0, jsx_runtime_1.jsx)(Controls, { uniforms: uniforms, onPatch: onPatch, disabled: disabled }, value) }));
@@ -1 +1 @@
1
- {"version":3,"file":"preset-control-panel.js","sourceRoot":"","sources":["../../../../src/components/preset-control-panel/preset-control-panel.tsx"],"names":[],"mappings":";;;;AAWA,yCAAoD;AAWpD,4BAAqE,EACnE,OAAO,EACP,KAAK,EACL,OAAO,EACP,QAAQ,GAAG,KAAK,GACW;IAC3B,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,QAAQ,GAAG,MAAM,EAAE,QAAQ,CAAC;IAClC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,gFAAgF;IAChF,2DAA2D;IAC3D,MAAM,QAAQ,GAA+D,EAAE,CAAC;IAChF,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,UAAU,IAAI,KAAK,EAAE,CAAC;YACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAgD,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,OAAO,CACL,uBAAC,2BAAmB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACxC,uBAAC,QAAQ,IAAa,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,IAA/D,KAAK,CAA8D,GACrD,CAChC,CAAC;AACJ,CAAC","sourcesContent":["// PresetControlPanel: the thin, controlled editor. For the active preset it\n// renders that preset's `controls` component (keyed by preset id, so a switch\n// remounts and the ControlForms re-seed), handing it the per-layer baked\n// uniforms and a single shared onPatch. It never calls `kaleidoscope` itself;\n// the host routes onPatch into `kaleidoscope(activeId, [patch])`.\n\nimport type { ReactElement } from 'react';\nimport type {\n KaleidoscopeControls,\n KaleidoscopePresetBook,\n} from '../../kaleidoscope.preset-book.types';\nimport { ControlScopeContext } from '../form/scope';\n\nexport type PresetControlPanelProps<P extends KaleidoscopePresetBook> = {\n readonly presets: P;\n /** The active preset id, or null when nothing is selected. */\n readonly value: (keyof P & string) | null;\n /** Routed to the host, which applies it via `kaleidoscope(value, [patch])`. */\n readonly onPatch: KaleidoscopeControls['onPatch'];\n readonly disabled?: boolean;\n};\n\nexport function PresetControlPanel<P extends KaleidoscopePresetBook>({\n presets,\n value,\n onPatch,\n disabled = false,\n}: PresetControlPanelProps<P>): ReactElement | null {\n if (value === null) return null;\n const preset = presets[value];\n const Controls = preset?.controls;\n if (!Controls) return null;\n\n // Per-layer baked uniforms keyed by id, for the controls component to seed each\n // layer's ControlForm. Only tunable layers carry uniforms.\n const uniforms: Record<string, Record<string, number | readonly number[]>> = {};\n for (const layer of preset.layers) {\n if ('uniforms' in layer) {\n uniforms[layer.id] = { ...layer.uniforms } as Record<string, number | readonly number[]>;\n }\n }\n\n return (\n <ControlScopeContext.Provider value={value}>\n <Controls key={value} uniforms={uniforms} onPatch={onPatch} disabled={disabled} />\n </ControlScopeContext.Provider>\n );\n}\n"]}
1
+ {"version":3,"file":"preset-control-panel.js","sourceRoot":"","sources":["../../../../src/components/preset-control-panel/preset-control-panel.tsx"],"names":[],"mappings":";;;;AAWA,yCAAoD;AAmBpD,4BAAqE,EACnE,OAAO,EACP,KAAK,EACL,OAAO,EACP,OAAO,EACP,QAAQ,GAAG,KAAK,GACW;IAC3B,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,QAAQ,GAAG,MAAM,EAAE,QAAQ,CAAC;IAClC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,gFAAgF;IAChF,4EAA4E;IAC5E,sEAAsE;IACtE,MAAM,QAAQ,GAA+D,EAAE,CAAC;IAChF,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,UAAU,IAAI,KAAK,EAAE,CAAC;YACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG;gBACnB,GAAG,KAAK,CAAC,QAAQ;gBACjB,GAAG,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;aACuB,CAAC;QAClD,CAAC;IACH,CAAC;IAED,OAAO,CACL,uBAAC,2BAAmB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YACxC,uBAAC,QAAQ,IAAa,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,IAA/D,KAAK,CAA8D,GACrD,CAChC,CAAC;AACJ,CAAC","sourcesContent":["// PresetControlPanel: the thin, controlled editor. For the active preset it\n// renders that preset's `controls` component (keyed by preset id, so a switch\n// remounts and the ControlForms re-seed), handing it the per-layer baked\n// uniforms and a single shared onPatch. It never calls `kaleidoscope` itself;\n// the host routes onPatch into `kaleidoscope(activeId, [patch])`.\n\nimport type { ReactElement } from 'react';\nimport type {\n KaleidoscopeControls,\n KaleidoscopePresetBook,\n} from '../../kaleidoscope.preset-book.types';\nimport { ControlScopeContext } from '../form/scope';\n\nexport type PresetControlPanelProps<P extends KaleidoscopePresetBook> = {\n readonly presets: P;\n /** The active preset id, or null when nothing is selected. */\n readonly value: (keyof P & string) | null;\n /** Routed to the host, which applies it via `kaleidoscope(value, [patch])`. */\n readonly onPatch: KaleidoscopeControls['onPatch'];\n /**\n * Per-layer uniform overrides (e.g. a persisted selection's stored patches)\n * merged over the preset's baked uniforms when the forms seed. Seed-time\n * only: the forms re-seed on a preset switch, not when this prop changes\n * mid-mount, so hosts restoring persisted patches should mount the panel\n * after hydration.\n */\n readonly patches?: KaleidoscopeControls['uniforms'];\n readonly disabled?: boolean;\n};\n\nexport function PresetControlPanel<P extends KaleidoscopePresetBook>({\n presets,\n value,\n onPatch,\n patches,\n disabled = false,\n}: PresetControlPanelProps<P>): ReactElement | null {\n if (value === null) return null;\n const preset = presets[value];\n const Controls = preset?.controls;\n if (!Controls) return null;\n\n // Per-layer baked uniforms keyed by id, for the controls component to seed each\n // layer's ControlForm. Only tunable layers carry uniforms; stored overrides\n // merge over the baked values so restored tweaks appear in the forms.\n const uniforms: Record<string, Record<string, number | readonly number[]>> = {};\n for (const layer of preset.layers) {\n if ('uniforms' in layer) {\n uniforms[layer.id] = {\n ...layer.uniforms,\n ...patches?.[layer.id],\n } as Record<string, number | readonly number[]>;\n }\n }\n\n return (\n <ControlScopeContext.Provider value={value}>\n <Controls key={value} uniforms={uniforms} onPatch={onPatch} disabled={disabled} />\n </ControlScopeContext.Provider>\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"controls.d.ts","sourceRoot":"","sources":["../../../src/kaleidoscope/controls.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAsB,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AACpG,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,uBAAuB,EAAkB,MAAM,SAAS,CAAC;AAE5F,uEAAuE;AACvE,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,gBAAgB,CAAC;IAC9D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,kEAAkE;AAClE,MAAM,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;AAEpE;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,KAC3D,IAAI,CAAC;AAEV;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,IAAI,CAAC;AAiB5C,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,sBAAsB,aAClD,gBAAgB,wBACL,uBAAuB,CAAC,CAAC,CAAC,aACrC,SAAS,WACX,OAAO,oBACE,gBAAgB,sBACd,kBAAkB,KACrC,mBAAmB,CAAC,CAAC,CA0DvB,CAAC"}
1
+ {"version":3,"file":"controls.d.ts","sourceRoot":"","sources":["../../../src/kaleidoscope/controls.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAsB,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AACpG,OAAO,KAAK,EAAE,UAAU,EAAiB,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAAE,mBAAmB,EAAE,uBAAuB,EAAkB,MAAM,SAAS,CAAC;AAE5F,uEAAuE;AACvE,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,gBAAgB,CAAC;IAC9D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,kEAAkE;AAClE,MAAM,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;AAEpE;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,KAC3D,IAAI,CAAC;AAEV;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,IAAI,CAAC;AAiB5C,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,sBAAsB,aAClD,gBAAgB,wBACL,uBAAuB,CAAC,CAAC,CAAC,aACrC,SAAS,WACX,OAAO,oBACE,gBAAgB,sBACd,kBAAkB,KACrC,mBAAmB,CAAC,CAAC,CA6DvB,CAAC"}
@@ -65,10 +65,13 @@ const createControls = (baseTrack, { presets, onTrack }, reconcile, setMask, set
65
65
  // Switch the preset (or clear): rebuild. Drop every live override first so
66
66
  // a reused layer id (e.g. 'blur', shared by the low/medium/high blur
67
67
  // presets) takes the new preset's baked uniforms instead of carrying a
68
- // stale slider override across. A transform rebuild does NOT pass through
69
- // here, so slider tweaks survive flips/rotations of the active preset.
68
+ // stale slider override across. Patches given WITH the switch (e.g. a
69
+ // persisted selection's overrides) merge into the rebuilt stack itself,
70
+ // so they land on every platform, native included. A transform rebuild
71
+ // does NOT pass through here, so slider tweaks survive flips/rotations
72
+ // of the active preset.
70
73
  activeId = cmd;
71
- art = cmd == null ? null : (0, shader_to_spec_1.compositeToEffectSpec)(presets[cmd]);
74
+ art = cmd == null ? null : (0, shader_to_spec_1.compositeToEffectSpec)(presets[cmd], patches);
72
75
  resetLayerUniforms();
73
76
  apply();
74
77
  },
@@ -1 +1 @@
1
- {"version":3,"file":"controls.js","sourceRoot":"","sources":["../../../src/kaleidoscope/controls.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,0EAA0E;AAC1E,+EAA+E;AAC/E,gFAAgF;AAChF,gEAAgE;AAChE,EAAE;AACF,+EAA+E;AAC/E,uEAAuE;AACvE,iFAAiF;AACjF,mDAAmD;;;AAInD,qDAAyD;AA6BzD,6EAA6E;AAC7E,iFAAiF;AACjF,+DAA+D;AAC/D,MAAM,kBAAkB,GAAG,CAAC,CAAkB,EAAgB,EAAE;IAC9D,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAClB,MAAM,KAAK,GAAoB,EAAE,CAAC;IAClC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IAC1E,IAAI,GAAG,KAAK,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;SACnC,IAAI,GAAG,KAAK,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;SACtD,IAAI,GAAG,KAAK,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC,CAAC;AAEK,MAAM,cAAc,GAAG,CAC5B,SAA2B,EAC3B,EAAE,OAAO,EAAE,OAAO,EAA8B,EAChD,SAAoB,EACpB,OAAgB,EAChB,gBAAkC,EAClC,kBAAsC,EACd,EAAE;IAC1B,IAAI,GAAG,GAAsB,IAAI,CAAC;IAClC,IAAI,YAAY,GAAiB,EAAE,CAAC;IACpC,IAAI,OAAO,GAAG,SAAS,CAAC;IACxB,6EAA6E;IAC7E,oDAAoD;IACpD,IAAI,QAAQ,GAAmB,IAAI,CAAC;IAEpC,MAAM,KAAK,GAAG,GAAS,EAAE;QACvB,MAAM,KAAK,GAAiB,EAAE,CAAC;QAC/B,IAAI,GAAG;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,CAAC;QAC5B,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC,CAAC;IAEF,OAAO;QACL,YAAY,EAAE,CACZ,GAAmB,EACnB,OAGE,EACF,EAAE;YACF,uEAAuE;YACvE,wEAAwE;YACxE,2CAA2C;YAC3C,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,KAAK,QAAQ,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;oBAC5B,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC7C,CAAC;gBACD,OAAO;YACT,CAAC;YACD,2EAA2E;YAC3E,qEAAqE;YACrE,uEAAuE;YACvE,0EAA0E;YAC1E,uEAAuE;YACvE,QAAQ,GAAG,GAAG,CAAC;YACf,GAAG,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAA,sCAAqB,EAAC,OAAO,CAAC,GAAG,CAAuB,CAAC,CAAC;YACrF,kBAAkB,EAAE,CAAC;YACrB,KAAK,EAAE,CAAC;QACV,CAAC;QACD,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;YACf,YAAY,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;YACrC,KAAK,EAAE,CAAC;QACV,CAAC;QACD,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;YACV,uEAAuE;YACvE,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,KAAK;YACP,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,OAAO,EAAE,GAAG,EAAE;YACZ,SAAS,CAAC,OAAO,EAAE,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAjEW,QAAA,cAAc,GAAd,cAAc,CAiEzB","sourcesContent":["// The three-verb controls: shared composite-state machine, platform-agnostic.\n//\n// Holds the art effect (one composite) and the transform op list, reconciles\n// them into an ordered EffectSpec array (art FIRST so segmentation sees the\n// upright frame, transform LAST so it reorients the finished composite), and\n// applies them through an injected platform `reconcile`. Web rebuilds the\n// pipeline and yields a new track (disposing the prior one); native mutates in\n// place. `mask` writes the segmentation edge through an injected `setMask`; the\n// running composite reads it per frame, so it needs no rebuild.\n//\n// The art verb is rebuild-aware: switching to a different preset rebuilds, but\n// patching the currently-active preset routes through an injected live\n// layer-uniform channel (`setLayerUniforms`, keyed by layer id) so a slider drag\n// updates the running composite without a rebuild.\n\nimport type { KaleidoscopePreset, KaleidoscopePresetBook } from '../kaleidoscope.preset-book.types';\nimport type { EffectSpec, TransformName } from './effect.types';\nimport { compositeToEffectSpec } from './shader-to-spec';\nimport type { KaleidoscopeBinding, KaleidoscopeBindOptions, TransformInput } from './types';\n\n/** Apply the ordered specs to the base track and return the output. */\nexport type Reconcile = {\n apply: (specs: ReadonlyArray<EffectSpec>) => MediaStreamTrack;\n dispose: () => void;\n};\n\n/** Write the segmentation mask edge (platform tuning channel). */\nexport type SetMask = (hardness: number, threshold: number) => void;\n\n/**\n * Write a live per-layer uniform override (platform tuning channel), keyed by\n * layer id. The running composite merges these over the layer's baked uniforms\n * each frame, with no rebuild. Mirrors `setMask`.\n */\nexport type SetLayerUniforms = (\n id: string,\n uniforms: Readonly<Record<string, number | readonly number[]>>,\n) => void;\n\n/**\n * Drop every live per-layer override (platform tuning channel). A preset switch\n * calls this so a reused layer id reverts to the new preset's baked uniforms\n * rather than inheriting a stale override from the prior preset.\n */\nexport type ResetLayerUniforms = () => void;\n\n// Decompose an absolute transform into the discrete ops the pipeline already\n// runs (reused on web and native). Flips first, then rotation; rotation snaps to\n// the nearest 90°. 180° is two CW steps; 270° is one CCW step.\nconst decomposeTransform = (t?: TransformInput): EffectSpec[] => {\n if (!t) return [];\n const names: TransformName[] = [];\n if (t.flip?.x) names.push('flip-x');\n if (t.flip?.y) names.push('flip-y');\n const deg = (((Math.round((t.rotate ?? 0) / 90) * 90) % 360) + 360) % 360;\n if (deg === 90) names.push('rotate-cw');\n else if (deg === 180) names.push('rotate-cw', 'rotate-cw');\n else if (deg === 270) names.push('rotate-ccw');\n return names.map((name) => ({ name }));\n};\n\nexport const createControls = <P extends KaleidoscopePresetBook>(\n baseTrack: MediaStreamTrack,\n { presets, onTrack }: KaleidoscopeBindOptions<P>,\n reconcile: Reconcile,\n setMask: SetMask,\n setLayerUniforms: SetLayerUniforms,\n resetLayerUniforms: ResetLayerUniforms,\n): KaleidoscopeBinding<P> => {\n let art: EffectSpec | null = null;\n let transformOps: EffectSpec[] = [];\n let current = baseTrack;\n // The id of the active preset (null when cleared). A patch of THIS id routes\n // through the live channel; any other cmd rebuilds.\n let activeId: keyof P | null = null;\n\n const apply = (): void => {\n const specs: EffectSpec[] = [];\n if (art) specs.push(art);\n specs.push(...transformOps);\n current = reconcile.apply(specs);\n onTrack?.(current);\n };\n\n return {\n kaleidoscope: (\n cmd: keyof P | null,\n patches?: ReadonlyArray<{\n readonly id: string;\n readonly uniforms: Readonly<Record<string, number | readonly number[]>>;\n }>,\n ) => {\n // Patch the currently-active preset: route through the live no-rebuild\n // channel, keyed by layer id. The `shader` field on a patch is only for\n // narrowing; the channel resolves by `id`.\n if (cmd != null && cmd === activeId && patches && patches.length > 0) {\n for (const patch of patches) {\n setLayerUniforms(patch.id, patch.uniforms);\n }\n return;\n }\n // Switch the preset (or clear): rebuild. Drop every live override first so\n // a reused layer id (e.g. 'blur', shared by the low/medium/high blur\n // presets) takes the new preset's baked uniforms instead of carrying a\n // stale slider override across. A transform rebuild does NOT pass through\n // here, so slider tweaks survive flips/rotations of the active preset.\n activeId = cmd;\n art = cmd == null ? null : compositeToEffectSpec(presets[cmd] as KaleidoscopePreset);\n resetLayerUniforms();\n apply();\n },\n transform: (t) => {\n transformOps = decomposeTransform(t);\n apply();\n },\n mask: (m) => {\n // Updates the edge the per-frame composite reads; no pipeline rebuild.\n setMask(m.hardness, m.threshold);\n },\n get track() {\n return current;\n },\n dispose: () => {\n reconcile.dispose();\n },\n };\n};\n"]}
1
+ {"version":3,"file":"controls.js","sourceRoot":"","sources":["../../../src/kaleidoscope/controls.ts"],"names":[],"mappings":"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","sourcesContent":["// The three-verb controls: shared composite-state machine, platform-agnostic.\n//\n// Holds the art effect (one composite) and the transform op list, reconciles\n// them into an ordered EffectSpec array (art FIRST so segmentation sees the\n// upright frame, transform LAST so it reorients the finished composite), and\n// applies them through an injected platform `reconcile`. Web rebuilds the\n// pipeline and yields a new track (disposing the prior one); native mutates in\n// place. `mask` writes the segmentation edge through an injected `setMask`; the\n// running composite reads it per frame, so it needs no rebuild.\n//\n// The art verb is rebuild-aware: switching to a different preset rebuilds, but\n// patching the currently-active preset routes through an injected live\n// layer-uniform channel (`setLayerUniforms`, keyed by layer id) so a slider drag\n// updates the running composite without a rebuild.\n\nimport type { KaleidoscopePreset, KaleidoscopePresetBook } from '../kaleidoscope.preset-book.types';\nimport type { EffectSpec, TransformName } from './effect.types';\nimport { compositeToEffectSpec } from './shader-to-spec';\nimport type { KaleidoscopeBinding, KaleidoscopeBindOptions, TransformInput } from './types';\n\n/** Apply the ordered specs to the base track and return the output. */\nexport type Reconcile = {\n apply: (specs: ReadonlyArray<EffectSpec>) => MediaStreamTrack;\n dispose: () => void;\n};\n\n/** Write the segmentation mask edge (platform tuning channel). */\nexport type SetMask = (hardness: number, threshold: number) => void;\n\n/**\n * Write a live per-layer uniform override (platform tuning channel), keyed by\n * layer id. The running composite merges these over the layer's baked uniforms\n * each frame, with no rebuild. Mirrors `setMask`.\n */\nexport type SetLayerUniforms = (\n id: string,\n uniforms: Readonly<Record<string, number | readonly number[]>>,\n) => void;\n\n/**\n * Drop every live per-layer override (platform tuning channel). A preset switch\n * calls this so a reused layer id reverts to the new preset's baked uniforms\n * rather than inheriting a stale override from the prior preset.\n */\nexport type ResetLayerUniforms = () => void;\n\n// Decompose an absolute transform into the discrete ops the pipeline already\n// runs (reused on web and native). Flips first, then rotation; rotation snaps to\n// the nearest 90°. 180° is two CW steps; 270° is one CCW step.\nconst decomposeTransform = (t?: TransformInput): EffectSpec[] => {\n if (!t) return [];\n const names: TransformName[] = [];\n if (t.flip?.x) names.push('flip-x');\n if (t.flip?.y) names.push('flip-y');\n const deg = (((Math.round((t.rotate ?? 0) / 90) * 90) % 360) + 360) % 360;\n if (deg === 90) names.push('rotate-cw');\n else if (deg === 180) names.push('rotate-cw', 'rotate-cw');\n else if (deg === 270) names.push('rotate-ccw');\n return names.map((name) => ({ name }));\n};\n\nexport const createControls = <P extends KaleidoscopePresetBook>(\n baseTrack: MediaStreamTrack,\n { presets, onTrack }: KaleidoscopeBindOptions<P>,\n reconcile: Reconcile,\n setMask: SetMask,\n setLayerUniforms: SetLayerUniforms,\n resetLayerUniforms: ResetLayerUniforms,\n): KaleidoscopeBinding<P> => {\n let art: EffectSpec | null = null;\n let transformOps: EffectSpec[] = [];\n let current = baseTrack;\n // The id of the active preset (null when cleared). A patch of THIS id routes\n // through the live channel; any other cmd rebuilds.\n let activeId: keyof P | null = null;\n\n const apply = (): void => {\n const specs: EffectSpec[] = [];\n if (art) specs.push(art);\n specs.push(...transformOps);\n current = reconcile.apply(specs);\n onTrack?.(current);\n };\n\n return {\n kaleidoscope: (\n cmd: keyof P | null,\n patches?: ReadonlyArray<{\n readonly id: string;\n readonly uniforms: Readonly<Record<string, number | readonly number[]>>;\n }>,\n ) => {\n // Patch the currently-active preset: route through the live no-rebuild\n // channel, keyed by layer id. The `shader` field on a patch is only for\n // narrowing; the channel resolves by `id`.\n if (cmd != null && cmd === activeId && patches && patches.length > 0) {\n for (const patch of patches) {\n setLayerUniforms(patch.id, patch.uniforms);\n }\n return;\n }\n // Switch the preset (or clear): rebuild. Drop every live override first so\n // a reused layer id (e.g. 'blur', shared by the low/medium/high blur\n // presets) takes the new preset's baked uniforms instead of carrying a\n // stale slider override across. Patches given WITH the switch (e.g. a\n // persisted selection's overrides) merge into the rebuilt stack itself,\n // so they land on every platform, native included. A transform rebuild\n // does NOT pass through here, so slider tweaks survive flips/rotations\n // of the active preset.\n activeId = cmd;\n art = cmd == null ? null : compositeToEffectSpec(presets[cmd] as KaleidoscopePreset, patches);\n resetLayerUniforms();\n apply();\n },\n transform: (t) => {\n transformOps = decomposeTransform(t);\n apply();\n },\n mask: (m) => {\n // Updates the edge the per-frame composite reads; no pipeline rebuild.\n setMask(m.hardness, m.threshold);\n },\n get track() {\n return current;\n },\n dispose: () => {\n reconcile.dispose();\n },\n };\n};\n"]}
@@ -1,4 +1,18 @@
1
1
  import type { KaleidoscopePreset } from '../kaleidoscope.preset-book.types';
2
2
  import type { EffectSpec } from './effect.types';
3
- export declare const compositeToEffectSpec: (composite: KaleidoscopePreset) => EffectSpec;
3
+ /** The patch wire shape (layer id + partial uniforms), as the verb receives it. */
4
+ type LayerPatchInput = {
5
+ readonly id: string;
6
+ readonly uniforms: Readonly<Record<string, number | readonly number[]>>;
7
+ };
8
+ /**
9
+ * Project a composite into the spec, optionally merging per-layer uniform
10
+ * patches over the baked values (a switch-with-patches, e.g. restoring a
11
+ * persisted selection). Merging here, at the seam, is what carries the patches
12
+ * to EVERY platform: web rebuilds from these layers, and native re-sends them
13
+ * over setCompositeLayers. A patch addressing a non-tunable or unknown layer id
14
+ * is ignored.
15
+ */
16
+ export declare const compositeToEffectSpec: (composite: KaleidoscopePreset, patches?: ReadonlyArray<LayerPatchInput>) => EffectSpec;
17
+ export {};
4
18
  //# sourceMappingURL=shader-to-spec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shader-to-spec.d.ts","sourceRoot":"","sources":["../../../src/kaleidoscope/shader-to-spec.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,eAAO,MAAM,qBAAqB,cAAe,kBAAkB,KAAG,UAGpE,CAAC"}
1
+ {"version":3,"file":"shader-to-spec.d.ts","sourceRoot":"","sources":["../../../src/kaleidoscope/shader-to-spec.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAqB,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAC/F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,mFAAmF;AACnF,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CACzE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,cACrB,kBAAkB,YACnB,aAAa,CAAC,eAAe,CAAC,KACvC,UAaF,CAAC"}
@@ -9,9 +9,28 @@
9
9
  // blend. Transforms are not book entries; the transform verb handles them.
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.compositeToEffectSpec = void 0;
12
- const compositeToEffectSpec = (composite) => ({
13
- name: 'composite',
14
- layers: composite.layers,
15
- });
12
+ /**
13
+ * Project a composite into the spec, optionally merging per-layer uniform
14
+ * patches over the baked values (a switch-with-patches, e.g. restoring a
15
+ * persisted selection). Merging here, at the seam, is what carries the patches
16
+ * to EVERY platform: web rebuilds from these layers, and native re-sends them
17
+ * over setCompositeLayers. A patch addressing a non-tunable or unknown layer id
18
+ * is ignored.
19
+ */
20
+ const compositeToEffectSpec = (composite, patches) => {
21
+ if (!patches || patches.length === 0) {
22
+ return { name: 'composite', layers: composite.layers };
23
+ }
24
+ const byId = new Map(patches.map((patch) => [patch.id, patch.uniforms]));
25
+ return {
26
+ name: 'composite',
27
+ layers: composite.layers.map((layer) => {
28
+ const override = byId.get(layer.id);
29
+ if (!override || !('uniforms' in layer))
30
+ return layer;
31
+ return { ...layer, uniforms: { ...layer.uniforms, ...override } };
32
+ }),
33
+ };
34
+ };
16
35
  exports.compositeToEffectSpec = compositeToEffectSpec;
17
36
  //# sourceMappingURL=shader-to-spec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shader-to-spec.js","sourceRoot":"","sources":["../../../src/kaleidoscope/shader-to-spec.ts"],"names":[],"mappings":";AAAA,2EAA2E;AAC3E,iFAAiF;AACjF,8EAA8E;AAC9E,wCAAwC;AACxC,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,2EAA2E;;;AAKpE,MAAM,qBAAqB,GAAG,CAAC,SAA6B,EAAc,EAAE,CAAC,CAAC;IACnF,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,SAAS,CAAC,MAAM;CACzB,CAAC,CAAC;AAHU,QAAA,qBAAqB,GAArB,qBAAqB,CAG/B","sourcesContent":["// Translate a book composite into the lower-level EffectSpec the primitive\n// `applyVideoEffects` consumes. This is the seam between the book vocabulary the\n// consumer sees and the effect the pipeline runs; the controls own the active\n// composite and reconcile through here.\n//\n// Every book entry is a composite (an ordered layer stack), so this is a thin\n// projection: the layers already carry their own ids, sources, uniforms, and\n// blend. Transforms are not book entries; the transform verb handles them.\n\nimport type { KaleidoscopePreset } from '../kaleidoscope.preset-book.types';\nimport type { EffectSpec } from './effect.types';\n\nexport const compositeToEffectSpec = (composite: KaleidoscopePreset): EffectSpec => ({\n name: 'composite',\n layers: composite.layers,\n});\n"]}
1
+ {"version":3,"file":"shader-to-spec.js","sourceRoot":"","sources":["../../../src/kaleidoscope/shader-to-spec.ts"],"names":[],"mappings":";AAAA,2EAA2E;AAC3E,iFAAiF;AACjF,8EAA8E;AAC9E,wCAAwC;AACxC,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,2EAA2E;;;AAW3E;;;;;;;GAOG;AACI,MAAM,qBAAqB,GAAG,CACnC,SAA6B,EAC7B,OAAwC,EAC5B,EAAE;IACd,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC;IACzD,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACpC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YACtD,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE,GAAG,QAAQ,EAAE,EAAuB,CAAC;QACzF,CAAC,CAAC;KACH,CAAC;AACJ,CAAC,CAAC;AAhBW,QAAA,qBAAqB,GAArB,qBAAqB,CAgBhC","sourcesContent":["// Translate a book composite into the lower-level EffectSpec the primitive\n// `applyVideoEffects` consumes. This is the seam between the book vocabulary the\n// consumer sees and the effect the pipeline runs; the controls own the active\n// composite and reconcile through here.\n//\n// Every book entry is a composite (an ordered layer stack), so this is a thin\n// projection: the layers already carry their own ids, sources, uniforms, and\n// blend. Transforms are not book entries; the transform verb handles them.\n\nimport type { KaleidoscopeLayer, KaleidoscopePreset } from '../kaleidoscope.preset-book.types';\nimport type { EffectSpec } from './effect.types';\n\n/** The patch wire shape (layer id + partial uniforms), as the verb receives it. */\ntype LayerPatchInput = {\n readonly id: string;\n readonly uniforms: Readonly<Record<string, number | readonly number[]>>;\n};\n\n/**\n * Project a composite into the spec, optionally merging per-layer uniform\n * patches over the baked values (a switch-with-patches, e.g. restoring a\n * persisted selection). Merging here, at the seam, is what carries the patches\n * to EVERY platform: web rebuilds from these layers, and native re-sends them\n * over setCompositeLayers. A patch addressing a non-tunable or unknown layer id\n * is ignored.\n */\nexport const compositeToEffectSpec = (\n composite: KaleidoscopePreset,\n patches?: ReadonlyArray<LayerPatchInput>,\n): EffectSpec => {\n if (!patches || patches.length === 0) {\n return { name: 'composite', layers: composite.layers };\n }\n const byId = new Map(patches.map((patch) => [patch.id, patch.uniforms]));\n return {\n name: 'composite',\n layers: composite.layers.map((layer) => {\n const override = byId.get(layer.id);\n if (!override || !('uniforms' in layer)) return layer;\n return { ...layer, uniforms: { ...layer.uniforms, ...override } } as KaleidoscopeLayer;\n }),\n };\n};\n"]}
@@ -58,7 +58,9 @@ export type KaleidoscopeBindOptions<P extends KaleidoscopePresetBook> = {
58
58
  * The art verb: select a composite by id (rebuilding the pipeline), or clear it
59
59
  * with `null`. When `cmd` is the currently-active preset id and `patches` is
60
60
  * given, the patches merge through the live no-rebuild uniform channel (keyed by
61
- * layer id) instead of rebuilding, so a slider drag stays smooth.
61
+ * layer id) instead of rebuilding, so a slider drag stays smooth. On a preset
62
+ * SWITCH, patches merge into the rebuilt layer stack itself, so a restored
63
+ * selection lands tuned on every platform, native included.
62
64
  */
63
65
  type KaleidoscopeCommand<P extends KaleidoscopePresetBook> = <K extends keyof P>(cmd: K | null, patches?: PatchesFor<P, K>) => void;
64
66
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/kaleidoscope/types.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,SAAS,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,mBAAmB,CAAC;CACtD,GACG;IAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,GACpE,KAAK,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,sBAAsB,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,aAAa,CACzF,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CACjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/D,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG;IACtB,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,sBAAsB,IAAI;IACtE,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtD,CAAC;AAEF;;;;;GAKG;AACH,KAAK,mBAAmB,CAAC,CAAC,SAAS,sBAAsB,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAC7E,GAAG,EAAE,CAAC,GAAG,IAAI,EACb,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,KACvB,IAAI,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,sBAAsB;IACnE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/kaleidoscope/types.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,SAAS,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,SAAS,mBAAmB,CAAC;CACtD,GACG;IAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,GACpE,KAAK,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,sBAAsB,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,aAAa,CACzF,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CACjC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,oCAAoC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/D,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG;IACtB,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,sBAAsB,IAAI;IACtE,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtD,CAAC;AAEF;;;;;;;GAOG;AACH,KAAK,mBAAmB,CAAC,CAAC,SAAS,sBAAsB,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,EAC7E,GAAG,EAAE,CAAC,GAAG,IAAI,EACb,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,KACvB,IAAI,CAAC;AAEV;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,sBAAsB;IACnE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC;CAC9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/kaleidoscope/types.ts"],"names":[],"mappings":";AAAA,iCAAiC;AACjC,EAAE;AACF,mEAAmE;AACnE,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,8EAA8E;AAC9E,kDAAkD;AAClD,kFAAkF;AAClF,kFAAkF;AAClF,EAAE;AACF,6EAA6E;AAC7E,uEAAuE;AACvE,+EAA+E;AAC/E,8EAA8E","sourcesContent":["// The three-verb surface: types.\n//\n// Bind a track and a preset book once; get three typed verbs back:\n// - kaleidoscope(cmd, patches?) the art axis: which composite (layer stack)\n// fills the frame. cmd is a preset id from the book (narrowed), or null to\n// clear. patches optionally merge per-layer uniform overrides (addressed by\n// layer id); patching the currently-active preset routes through the live\n// no-rebuild channel, so sliders stay smooth.\n// - transform(t?) the geometry axis: absolute flips + 90° rotation.\n// - mask(m) the segmentation edge shared by every art effect.\n//\n// Shaders live in the library; consumers add presets (composites) over them,\n// never new shaders. Per shader-world convention, numeric uniforms are\n// normalized 0..1 where practical; ranges are documented in JSDoc as hints for\n// IntelliSense and tooling, not enforced at runtime (validation is userland).\n\nimport type { PatchableShaderName, ShaderUniformsMap } from '../../catalog/shaders';\nimport type { KaleidoscopePresetBook } from '../kaleidoscope.preset-book.types';\n\n/**\n * A live per-layer uniform override for ONE layer, derived from the layer's own\n * type: `id` is the layer's id, `uniforms` is `Partial` of the shader's uniform\n * type (re-indexed from `ShaderUniformsMap` by the layer's literal `shader`).\n * Non-tunable layers (`image`, `direct`) distribute to `never`, so they cannot be\n * patched. The runtime resolves by `id`; the shader is never sent on the wire.\n */\nexport type PatchFor<L> = L extends {\n readonly id: infer I extends string;\n readonly shader: infer S extends PatchableShaderName;\n}\n ? { readonly id: I; readonly uniforms: Partial<ShaderUniformsMap[S]> }\n : never;\n\n/**\n * The patches `kaleidoscope` accepts for preset `K` in book `P`: per-layer\n * overrides, each addressed by one of that preset's tunable layer ids and typed\n * by that layer's shader. At a literal `cmd` call site this narrows to the\n * preset's ids/uniforms; with a variable `cmd` it widens to the book-wide union\n * and is runtime-checked by id.\n */\nexport type PatchesFor<P extends KaleidoscopePresetBook, K extends keyof P> = ReadonlyArray<\n PatchFor<P[K]['layers'][number]>\n>;\n\n/**\n * Absolute, stateless geometric transform. Every call is the full desired state\n * from the identity orientation: re-passing is the caller's responsibility, and\n * `transform()` (or `transform({})`) resets to identity. Rotation snaps to the\n * nearest 90°; arbitrary angles and offset are a later step.\n */\nexport type TransformInput = {\n /** Mirror flips about each axis. */\n readonly flip?: { readonly x?: boolean; readonly y?: boolean };\n /** Clockwise rotation in degrees; snapped to the nearest 90 (0/90/180/270). */\n readonly rotate?: number;\n};\n\n/** The segmentation mask edge, shared by every art effect (not transforms). */\nexport type MaskInput = {\n /** Edge hardness, 0..1. 0 = soft halo, 1 = near-step. */\n readonly hardness: number;\n /** Edge threshold, 0..1. Higher rejects low-confidence (chair-edge) pixels. */\n readonly threshold: number;\n};\n\nexport type KaleidoscopeBindOptions<P extends KaleidoscopePresetBook> = {\n /** The consumer's preset book. Declare it `as const satisfies KaleidoscopePresetBook`. */\n readonly presets: P;\n /**\n * Called with the live output track after every art/transform command. On web\n * each command yields a NEW MediaStreamTrack (the pipeline is rebuilt); on\n * native the same track is mutated in place and passed back.\n */\n readonly onTrack?: (track: MediaStreamTrack) => void;\n};\n\n/**\n * The art verb: select a composite by id (rebuilding the pipeline), or clear it\n * with `null`. When `cmd` is the currently-active preset id and `patches` is\n * given, the patches merge through the live no-rebuild uniform channel (keyed by\n * layer id) instead of rebuilding, so a slider drag stays smooth.\n */\ntype KaleidoscopeCommand<P extends KaleidoscopePresetBook> = <K extends keyof P>(\n cmd: K | null,\n patches?: PatchesFor<P, K>,\n) => void;\n\n/**\n * The three verbs for one bound track and book, plus the live track and a\n * teardown. `kaleidoscope` (preset switch) and `transform` rebuild the composite\n * (web yields a new track via onTrack); a `kaleidoscope` patch of the active\n * preset and `mask` both update what the running composite reads each frame, so\n * they need no rebuild.\n */\nexport interface KaleidoscopeBinding<P extends KaleidoscopePresetBook> {\n readonly kaleidoscope: KaleidoscopeCommand<P>;\n readonly transform: (t?: TransformInput) => void;\n readonly mask: (m: MaskInput) => void;\n readonly track: MediaStreamTrack;\n readonly dispose: () => void;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/kaleidoscope/types.ts"],"names":[],"mappings":";AAAA,iCAAiC;AACjC,EAAE;AACF,mEAAmE;AACnE,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,8EAA8E;AAC9E,kDAAkD;AAClD,kFAAkF;AAClF,kFAAkF;AAClF,EAAE;AACF,6EAA6E;AAC7E,uEAAuE;AACvE,+EAA+E;AAC/E,8EAA8E","sourcesContent":["// The three-verb surface: types.\n//\n// Bind a track and a preset book once; get three typed verbs back:\n// - kaleidoscope(cmd, patches?) the art axis: which composite (layer stack)\n// fills the frame. cmd is a preset id from the book (narrowed), or null to\n// clear. patches optionally merge per-layer uniform overrides (addressed by\n// layer id); patching the currently-active preset routes through the live\n// no-rebuild channel, so sliders stay smooth.\n// - transform(t?) the geometry axis: absolute flips + 90° rotation.\n// - mask(m) the segmentation edge shared by every art effect.\n//\n// Shaders live in the library; consumers add presets (composites) over them,\n// never new shaders. Per shader-world convention, numeric uniforms are\n// normalized 0..1 where practical; ranges are documented in JSDoc as hints for\n// IntelliSense and tooling, not enforced at runtime (validation is userland).\n\nimport type { PatchableShaderName, ShaderUniformsMap } from '../../catalog/shaders';\nimport type { KaleidoscopePresetBook } from '../kaleidoscope.preset-book.types';\n\n/**\n * A live per-layer uniform override for ONE layer, derived from the layer's own\n * type: `id` is the layer's id, `uniforms` is `Partial` of the shader's uniform\n * type (re-indexed from `ShaderUniformsMap` by the layer's literal `shader`).\n * Non-tunable layers (`image`, `direct`) distribute to `never`, so they cannot be\n * patched. The runtime resolves by `id`; the shader is never sent on the wire.\n */\nexport type PatchFor<L> = L extends {\n readonly id: infer I extends string;\n readonly shader: infer S extends PatchableShaderName;\n}\n ? { readonly id: I; readonly uniforms: Partial<ShaderUniformsMap[S]> }\n : never;\n\n/**\n * The patches `kaleidoscope` accepts for preset `K` in book `P`: per-layer\n * overrides, each addressed by one of that preset's tunable layer ids and typed\n * by that layer's shader. At a literal `cmd` call site this narrows to the\n * preset's ids/uniforms; with a variable `cmd` it widens to the book-wide union\n * and is runtime-checked by id.\n */\nexport type PatchesFor<P extends KaleidoscopePresetBook, K extends keyof P> = ReadonlyArray<\n PatchFor<P[K]['layers'][number]>\n>;\n\n/**\n * Absolute, stateless geometric transform. Every call is the full desired state\n * from the identity orientation: re-passing is the caller's responsibility, and\n * `transform()` (or `transform({})`) resets to identity. Rotation snaps to the\n * nearest 90°; arbitrary angles and offset are a later step.\n */\nexport type TransformInput = {\n /** Mirror flips about each axis. */\n readonly flip?: { readonly x?: boolean; readonly y?: boolean };\n /** Clockwise rotation in degrees; snapped to the nearest 90 (0/90/180/270). */\n readonly rotate?: number;\n};\n\n/** The segmentation mask edge, shared by every art effect (not transforms). */\nexport type MaskInput = {\n /** Edge hardness, 0..1. 0 = soft halo, 1 = near-step. */\n readonly hardness: number;\n /** Edge threshold, 0..1. Higher rejects low-confidence (chair-edge) pixels. */\n readonly threshold: number;\n};\n\nexport type KaleidoscopeBindOptions<P extends KaleidoscopePresetBook> = {\n /** The consumer's preset book. Declare it `as const satisfies KaleidoscopePresetBook`. */\n readonly presets: P;\n /**\n * Called with the live output track after every art/transform command. On web\n * each command yields a NEW MediaStreamTrack (the pipeline is rebuilt); on\n * native the same track is mutated in place and passed back.\n */\n readonly onTrack?: (track: MediaStreamTrack) => void;\n};\n\n/**\n * The art verb: select a composite by id (rebuilding the pipeline), or clear it\n * with `null`. When `cmd` is the currently-active preset id and `patches` is\n * given, the patches merge through the live no-rebuild uniform channel (keyed by\n * layer id) instead of rebuilding, so a slider drag stays smooth. On a preset\n * SWITCH, patches merge into the rebuilt layer stack itself, so a restored\n * selection lands tuned on every platform, native included.\n */\ntype KaleidoscopeCommand<P extends KaleidoscopePresetBook> = <K extends keyof P>(\n cmd: K | null,\n patches?: PatchesFor<P, K>,\n) => void;\n\n/**\n * The three verbs for one bound track and book, plus the live track and a\n * teardown. `kaleidoscope` (preset switch) and `transform` rebuild the composite\n * (web yields a new track via onTrack); a `kaleidoscope` patch of the active\n * preset and `mask` both update what the running composite reads each frame, so\n * they need no rebuild.\n */\nexport interface KaleidoscopeBinding<P extends KaleidoscopePresetBook> {\n readonly kaleidoscope: KaleidoscopeCommand<P>;\n readonly transform: (t?: TransformInput) => void;\n readonly mask: (m: MaskInput) => void;\n readonly track: MediaStreamTrack;\n readonly dispose: () => void;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import { type KaleidoscopeStateStore } from './state';
2
+ export declare const kaleidoscopeAsyncStorageStore: KaleidoscopeStateStore;
3
+ //# sourceMappingURL=async-storage-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-storage-store.d.ts","sourceRoot":"","sources":["../../../src/persistence/async-storage-store.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,sBAAsB,EAI5B,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,6BAA6B,EAAE,sBAe3C,CAAC"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ // The default backing store: AsyncStorage under the canonical key. Works on
3
+ // every platform (the web build is localStorage-backed).
4
+ //
5
+ // `@react-native-async-storage/async-storage` is an OPTIONAL peer dependency of
6
+ // the package: only the `/persistence` subpath touches it, and Metro resolves
7
+ // it at bundle time for any app that imports this subpath (even with a custom
8
+ // `store`; bundlers do not tree-shake the default away).
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.kaleidoscopeAsyncStorageStore = void 0;
14
+ const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
15
+ const state_1 = require("./state");
16
+ exports.kaleidoscopeAsyncStorageStore = {
17
+ load() {
18
+ return async_storage_1.default.getItem(state_1.KALEIDOSCOPE_STATE_KEY).then(state_1.parseStoredKaleidoscopeState, () => null);
19
+ },
20
+ save(state) {
21
+ return async_storage_1.default.setItem(state_1.KALEIDOSCOPE_STATE_KEY, (0, state_1.serializeKaleidoscopeState)(state)).then(() => undefined, (error) => {
22
+ console.warn('kaleidoscope: preset persistence save failed', error);
23
+ });
24
+ },
25
+ };
26
+ //# sourceMappingURL=async-storage-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-storage-store.js","sourceRoot":"","sources":["../../../src/persistence/async-storage-store.ts"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,yDAAyD;AACzD,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,8EAA8E;AAC9E,yDAAyD;;;;;;AAEzD,8FAAqE;AACrE,mCAMiB;AAEJ,QAAA,6BAA6B,GAA2B;IACnE,IAAI;QACF,OAAO,uBAAY,CAAC,OAAO,CAAC,8BAAsB,CAAC,CAAC,IAAI,CACtD,oCAA4B,EAC5B,GAAG,EAAE,CAAC,IAAI,CACX,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAA8B;QACjC,OAAO,uBAAY,CAAC,OAAO,CAAC,8BAAsB,EAAE,IAAA,kCAA0B,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CACzF,GAAG,EAAE,CAAC,SAAS,EACf,CAAC,KAAK,EAAE,EAAE;YACR,OAAO,CAAC,IAAI,CAAC,8CAA8C,EAAE,KAAK,CAAC,CAAC;QACtE,CAAC,CACF,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["// The default backing store: AsyncStorage under the canonical key. Works on\n// every platform (the web build is localStorage-backed).\n//\n// `@react-native-async-storage/async-storage` is an OPTIONAL peer dependency of\n// the package: only the `/persistence` subpath touches it, and Metro resolves\n// it at bundle time for any app that imports this subpath (even with a custom\n// `store`; bundlers do not tree-shake the default away).\n\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport {\n KALEIDOSCOPE_STATE_KEY,\n type KaleidoscopeStateStore,\n parseStoredKaleidoscopeState,\n type StoredKaleidoscopeState,\n serializeKaleidoscopeState,\n} from './state';\n\nexport const kaleidoscopeAsyncStorageStore: KaleidoscopeStateStore = {\n load(): Promise<StoredKaleidoscopeState | null> {\n return AsyncStorage.getItem(KALEIDOSCOPE_STATE_KEY).then(\n parseStoredKaleidoscopeState,\n () => null,\n );\n },\n save(state: StoredKaleidoscopeState): Promise<void> {\n return AsyncStorage.setItem(KALEIDOSCOPE_STATE_KEY, serializeKaleidoscopeState(state)).then(\n () => undefined,\n (error) => {\n console.warn('kaleidoscope: preset persistence save failed', error);\n },\n );\n },\n};\n"]}
@@ -0,0 +1,4 @@
1
+ export { kaleidoscopeAsyncStorageStore } from './async-storage-store';
2
+ export { KaleidoscopeStateProvider, type KaleidoscopeStateProviderProps, type KaleidoscopeStateValue, useKaleidoscopeState, } from './provider';
3
+ export { DEFAULT_MASK, KALEIDOSCOPE_STATE_KEY, type KaleidoscopeStateStore, parseStoredKaleidoscopeState, pruneStoredState, type StoredKaleidoscopeState, type StoredLayerUniforms, type StoredPatch, type StoredPatches, type StoredPatchMap, serializeKaleidoscopeState, } from './state';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/persistence/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EACL,yBAAyB,EACzB,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,0BAA0B,GAC3B,MAAM,SAAS,CAAC"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ // Subpath entry: react-native-webrtc-kaleidoscope/persistence
3
+ //
4
+ // The persisted-selection convenience: a provider + hook that keep the last
5
+ // applied preset, its control-panel patches, and the mask across launches.
6
+ // Storage-agnostic via `KaleidoscopeStateStore`; defaults to AsyncStorage
7
+ // (`@react-native-async-storage/async-storage`, an optional peer dependency
8
+ // required only by apps that use the default store).
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.serializeKaleidoscopeState = exports.pruneStoredState = exports.parseStoredKaleidoscopeState = exports.KALEIDOSCOPE_STATE_KEY = exports.DEFAULT_MASK = exports.useKaleidoscopeState = exports.KaleidoscopeStateProvider = exports.kaleidoscopeAsyncStorageStore = void 0;
11
+ var async_storage_store_1 = require("./async-storage-store");
12
+ Object.defineProperty(exports, "kaleidoscopeAsyncStorageStore", { enumerable: true, get: function () { return async_storage_store_1.kaleidoscopeAsyncStorageStore; } });
13
+ var provider_1 = require("./provider");
14
+ Object.defineProperty(exports, "KaleidoscopeStateProvider", { enumerable: true, get: function () { return provider_1.KaleidoscopeStateProvider; } });
15
+ Object.defineProperty(exports, "useKaleidoscopeState", { enumerable: true, get: function () { return provider_1.useKaleidoscopeState; } });
16
+ var state_1 = require("./state");
17
+ Object.defineProperty(exports, "DEFAULT_MASK", { enumerable: true, get: function () { return state_1.DEFAULT_MASK; } });
18
+ Object.defineProperty(exports, "KALEIDOSCOPE_STATE_KEY", { enumerable: true, get: function () { return state_1.KALEIDOSCOPE_STATE_KEY; } });
19
+ Object.defineProperty(exports, "parseStoredKaleidoscopeState", { enumerable: true, get: function () { return state_1.parseStoredKaleidoscopeState; } });
20
+ Object.defineProperty(exports, "pruneStoredState", { enumerable: true, get: function () { return state_1.pruneStoredState; } });
21
+ Object.defineProperty(exports, "serializeKaleidoscopeState", { enumerable: true, get: function () { return state_1.serializeKaleidoscopeState; } });
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/persistence/index.ts"],"names":[],"mappings":";AAAA,8DAA8D;AAC9D,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,4EAA4E;AAC5E,qDAAqD;;;AAErD,6DAAsE;AAA7D,oIAAA,6BAA6B,OAAA;AACtC,uCAKoB;AAJlB,qHAAA,yBAAyB,OAAA;AAGzB,gHAAA,oBAAoB,OAAA;AAEtB,iCAYiB;AAXf,qGAAA,YAAY,OAAA;AACZ,+GAAA,sBAAsB,OAAA;AAEtB,qHAAA,4BAA4B,OAAA;AAC5B,yGAAA,gBAAgB,OAAA;AAMhB,mHAAA,0BAA0B,OAAA","sourcesContent":["// Subpath entry: react-native-webrtc-kaleidoscope/persistence\n//\n// The persisted-selection convenience: a provider + hook that keep the last\n// applied preset, its control-panel patches, and the mask across launches.\n// Storage-agnostic via `KaleidoscopeStateStore`; defaults to AsyncStorage\n// (`@react-native-async-storage/async-storage`, an optional peer dependency\n// required only by apps that use the default store).\n\nexport { kaleidoscopeAsyncStorageStore } from './async-storage-store';\nexport {\n KaleidoscopeStateProvider,\n type KaleidoscopeStateProviderProps,\n type KaleidoscopeStateValue,\n useKaleidoscopeState,\n} from './provider';\nexport {\n DEFAULT_MASK,\n KALEIDOSCOPE_STATE_KEY,\n type KaleidoscopeStateStore,\n parseStoredKaleidoscopeState,\n pruneStoredState,\n type StoredKaleidoscopeState,\n type StoredLayerUniforms,\n type StoredPatch,\n type StoredPatches,\n type StoredPatchMap,\n serializeKaleidoscopeState,\n} from './state';\n"]}
@@ -0,0 +1,38 @@
1
+ import { type ReactElement, type ReactNode } from 'react';
2
+ import type { MaskInput, PatchesFor } from '../kaleidoscope/types';
3
+ import type { KaleidoscopePresetBook } from '../kaleidoscope.preset-book.types';
4
+ import { type KaleidoscopeStateStore, type StoredPatch, type StoredPatches } from './state';
5
+ export type KaleidoscopeStateValue<P extends KaleidoscopePresetBook = KaleidoscopePresetBook> = {
6
+ /** False until the persisted selection has been read; apply no effects before then. */
7
+ readonly hydrated: boolean;
8
+ /** The selected preset id, or null when nothing is selected. */
9
+ readonly presetId: (keyof P & string) | null;
10
+ /** The shared segmentation edge. */
11
+ readonly mask: MaskInput;
12
+ /** Every preset's stored per-layer overrides (keyed by preset id, then layer id). */
13
+ readonly patches: StoredPatches;
14
+ readonly setPreset: (presetId: (keyof P & string) | null) => void;
15
+ readonly setMask: (mask: MaskInput) => void;
16
+ /** Record one control-panel patch against a preset (and persist it). */
17
+ readonly setPatch: (presetId: keyof P & string, patch: StoredPatch) => void;
18
+ /** A preset's stored overrides in the array shape `kaleidoscope(id, patches)` takes. */
19
+ readonly patchesFor: <K extends keyof P & string>(presetId: K) => PatchesFor<P, K>;
20
+ /** Clear the stored selection back to defaults (and persist the cleared state). */
21
+ readonly reset: () => void;
22
+ };
23
+ export type KaleidoscopeStateProviderProps<P extends KaleidoscopePresetBook> = {
24
+ /** The consumer's preset book; stored state is pruned against it at hydrate. */
25
+ readonly presets: P;
26
+ /** The backing store. Defaults to the AsyncStorage store (lazily loaded). */
27
+ readonly store?: KaleidoscopeStateStore;
28
+ /** The mask used before hydration and after `reset`. Defaults to 0.5/0.5. */
29
+ readonly defaultMask?: MaskInput;
30
+ readonly children: ReactNode;
31
+ };
32
+ export declare function KaleidoscopeStateProvider<P extends KaleidoscopePresetBook>({ presets, store, defaultMask, children, }: KaleidoscopeStateProviderProps<P>): ReactElement;
33
+ /**
34
+ * The persisted selection plus its setters. Typed by the consumer's book:
35
+ * `useKaleidoscopeState<typeof presets>()`. Throws outside the provider.
36
+ */
37
+ export declare function useKaleidoscopeState<P extends KaleidoscopePresetBook = KaleidoscopePresetBook>(): KaleidoscopeStateValue<P>;
38
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/persistence/provider.tsx"],"names":[],"mappings":"AAoBA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEhF,OAAO,EAEL,KAAK,sBAAsB,EAI3B,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,sBAAsB,GAAG,sBAAsB,IAAI;IAC9F,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;IAC7C,oCAAoC;IACpC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IAClE,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC5E,wFAAwF;IACxF,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACnF,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;CAC5B,CAAC;AAIF,MAAM,MAAM,8BAA8B,CAAC,CAAC,SAAS,sBAAsB,IAAI;IAC7E,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,sBAAsB,CAAC;IACxC,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC9B,CAAC;AAQF,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,sBAAsB,EAAE,EAC1E,OAAO,EACP,KAAK,EACL,WAA0B,EAC1B,QAAQ,GACT,EAAE,8BAA8B,CAAC,CAAC,CAAC,GAAG,YAAY,CA4DlD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,SAAS,sBAAsB,GAAG,sBAAsB,KACtD,sBAAsB,CAAC,CAAC,CAAC,CAY7B"}
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KaleidoscopeStateProvider = KaleidoscopeStateProvider;
4
+ exports.useKaleidoscopeState = useKaleidoscopeState;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ // Persistence: the React surface. `KaleidoscopeStateProvider` hydrates the
7
+ // stored selection once at mount and writes through on every change;
8
+ // `useKaleidoscopeState` hands the host the hydrated values plus the setters.
9
+ //
10
+ // The provider owns STORAGE state only; it never binds a track or calls the
11
+ // verbs. The host applies the restored selection itself (gated on `hydrated`,
12
+ // so a stored preset is not flashed over by the default):
13
+ //
14
+ // const { hydrated, presetId, patchesFor, mask, ... } = useKaleidoscopeState<typeof presets>();
15
+ // useEffect(() => {
16
+ // if (!hydrated || !controls) return;
17
+ // if (presetId) controls.kaleidoscope(presetId, patchesFor(presetId));
18
+ // else controls.kaleidoscope(null);
19
+ // }, [hydrated, controls, presetId]);
20
+ //
21
+ // The backing store defaults to AsyncStorage; pass any `KaleidoscopeStateStore`
22
+ // to swap it. Importing this subpath is what brings the optional
23
+ // `@react-native-async-storage/async-storage` peer onto your bundle path
24
+ // (Metro resolves it at bundle time either way, so there is no lazy escape).
25
+ const react_1 = require("react");
26
+ const async_storage_store_1 = require("./async-storage-store");
27
+ const state_1 = require("./state");
28
+ const KaleidoscopeStateContext = (0, react_1.createContext)(null);
29
+ function KaleidoscopeStateProvider({ presets, store, defaultMask = state_1.DEFAULT_MASK, children, }) {
30
+ const [hydrated, setHydrated] = (0, react_1.useState)(false);
31
+ const [selection, setSelection] = (0, react_1.useState)({
32
+ presetId: null,
33
+ mask: defaultMask,
34
+ patches: {},
35
+ });
36
+ // A write before hydration wins over the stored value (the person acted; do
37
+ // not clobber their fresh choice with yesterday's).
38
+ const dirty = (0, react_1.useRef)(false);
39
+ // Pinned for the provider's lifetime: hydrate and every write-through go to
40
+ // the same store the first render saw.
41
+ const storeRef = (0, react_1.useRef)(store ?? async_storage_store_1.kaleidoscopeAsyncStorageStore);
42
+ // biome-ignore lint/correctness/useExhaustiveDependencies: hydrate exactly once; the book and store are bind-time constants.
43
+ (0, react_1.useEffect)(() => {
44
+ let cancelled = false;
45
+ storeRef.current.load().then((stored) => {
46
+ if (cancelled)
47
+ return;
48
+ if (stored && !dirty.current) {
49
+ const pruned = (0, state_1.pruneStoredState)(stored, presets);
50
+ setSelection({ presetId: pruned.presetId, mask: pruned.mask, patches: pruned.patches });
51
+ }
52
+ setHydrated(true);
53
+ }, () => {
54
+ if (!cancelled)
55
+ setHydrated(true);
56
+ });
57
+ return () => {
58
+ cancelled = true;
59
+ };
60
+ }, []);
61
+ const commit = (next) => {
62
+ dirty.current = true;
63
+ setSelection(next);
64
+ void storeRef.current.save({ version: 1, ...next });
65
+ };
66
+ const value = {
67
+ hydrated,
68
+ presetId: selection.presetId,
69
+ mask: selection.mask,
70
+ patches: selection.patches,
71
+ setPreset: (presetId) => commit({ ...selection, presetId }),
72
+ setMask: (mask) => commit({ ...selection, mask }),
73
+ setPatch: (presetId, patch) => commit({ ...selection, patches: (0, state_1.mergePatch)(selection.patches, presetId, patch) }),
74
+ // The stored wire shape is book-agnostic; the typed view is recovered at the
75
+ // hook (`useKaleidoscopeState<typeof presets>()`), so the cast is the seam.
76
+ patchesFor: ((presetId) => (0, state_1.patchListFor)(selection.patches, presetId)),
77
+ reset: () => commit({ presetId: null, mask: defaultMask, patches: {} }),
78
+ };
79
+ return ((0, jsx_runtime_1.jsx)(KaleidoscopeStateContext.Provider, { value: value, children: children }));
80
+ }
81
+ /**
82
+ * The persisted selection plus its setters. Typed by the consumer's book:
83
+ * `useKaleidoscopeState<typeof presets>()`. Throws outside the provider.
84
+ */
85
+ function useKaleidoscopeState() {
86
+ const value = (0, react_1.useContext)(KaleidoscopeStateContext);
87
+ if (value === null) {
88
+ throw new Error('useKaleidoscopeState: no <KaleidoscopeStateProvider> above this component. ' +
89
+ 'Wrap your app (or the screen using the picker) in the provider from ' +
90
+ "'react-native-webrtc-kaleidoscope/persistence'.");
91
+ }
92
+ // Safe by construction: the provider pruned ids against the same book the
93
+ // consumer parameterizes with; the runtime shapes are identical.
94
+ return value;
95
+ }
96
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../src/persistence/provider.tsx"],"names":[],"mappings":"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","sourcesContent":["// Persistence: the React surface. `KaleidoscopeStateProvider` hydrates the\n// stored selection once at mount and writes through on every change;\n// `useKaleidoscopeState` hands the host the hydrated values plus the setters.\n//\n// The provider owns STORAGE state only; it never binds a track or calls the\n// verbs. The host applies the restored selection itself (gated on `hydrated`,\n// so a stored preset is not flashed over by the default):\n//\n// const { hydrated, presetId, patchesFor, mask, ... } = useKaleidoscopeState<typeof presets>();\n// useEffect(() => {\n// if (!hydrated || !controls) return;\n// if (presetId) controls.kaleidoscope(presetId, patchesFor(presetId));\n// else controls.kaleidoscope(null);\n// }, [hydrated, controls, presetId]);\n//\n// The backing store defaults to AsyncStorage; pass any `KaleidoscopeStateStore`\n// to swap it. Importing this subpath is what brings the optional\n// `@react-native-async-storage/async-storage` peer onto your bundle path\n// (Metro resolves it at bundle time either way, so there is no lazy escape).\n\nimport {\n createContext,\n type ReactElement,\n type ReactNode,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport type { MaskInput, PatchesFor } from '../kaleidoscope/types';\nimport type { KaleidoscopePresetBook } from '../kaleidoscope.preset-book.types';\nimport { kaleidoscopeAsyncStorageStore } from './async-storage-store';\nimport {\n DEFAULT_MASK,\n type KaleidoscopeStateStore,\n mergePatch,\n patchListFor,\n pruneStoredState,\n type StoredPatch,\n type StoredPatches,\n} from './state';\n\nexport type KaleidoscopeStateValue<P extends KaleidoscopePresetBook = KaleidoscopePresetBook> = {\n /** False until the persisted selection has been read; apply no effects before then. */\n readonly hydrated: boolean;\n /** The selected preset id, or null when nothing is selected. */\n readonly presetId: (keyof P & string) | null;\n /** The shared segmentation edge. */\n readonly mask: MaskInput;\n /** Every preset's stored per-layer overrides (keyed by preset id, then layer id). */\n readonly patches: StoredPatches;\n readonly setPreset: (presetId: (keyof P & string) | null) => void;\n readonly setMask: (mask: MaskInput) => void;\n /** Record one control-panel patch against a preset (and persist it). */\n readonly setPatch: (presetId: keyof P & string, patch: StoredPatch) => void;\n /** A preset's stored overrides in the array shape `kaleidoscope(id, patches)` takes. */\n readonly patchesFor: <K extends keyof P & string>(presetId: K) => PatchesFor<P, K>;\n /** Clear the stored selection back to defaults (and persist the cleared state). */\n readonly reset: () => void;\n};\n\nconst KaleidoscopeStateContext = createContext<KaleidoscopeStateValue | null>(null);\n\nexport type KaleidoscopeStateProviderProps<P extends KaleidoscopePresetBook> = {\n /** The consumer's preset book; stored state is pruned against it at hydrate. */\n readonly presets: P;\n /** The backing store. Defaults to the AsyncStorage store (lazily loaded). */\n readonly store?: KaleidoscopeStateStore;\n /** The mask used before hydration and after `reset`. Defaults to 0.5/0.5. */\n readonly defaultMask?: MaskInput;\n readonly children: ReactNode;\n};\n\ntype Selection = {\n readonly presetId: string | null;\n readonly mask: MaskInput;\n readonly patches: StoredPatches;\n};\n\nexport function KaleidoscopeStateProvider<P extends KaleidoscopePresetBook>({\n presets,\n store,\n defaultMask = DEFAULT_MASK,\n children,\n}: KaleidoscopeStateProviderProps<P>): ReactElement {\n const [hydrated, setHydrated] = useState(false);\n const [selection, setSelection] = useState<Selection>({\n presetId: null,\n mask: defaultMask,\n patches: {},\n });\n // A write before hydration wins over the stored value (the person acted; do\n // not clobber their fresh choice with yesterday's).\n const dirty = useRef(false);\n // Pinned for the provider's lifetime: hydrate and every write-through go to\n // the same store the first render saw.\n const storeRef = useRef(store ?? kaleidoscopeAsyncStorageStore);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: hydrate exactly once; the book and store are bind-time constants.\n useEffect(() => {\n let cancelled = false;\n storeRef.current.load().then(\n (stored) => {\n if (cancelled) return;\n if (stored && !dirty.current) {\n const pruned = pruneStoredState(stored, presets);\n setSelection({ presetId: pruned.presetId, mask: pruned.mask, patches: pruned.patches });\n }\n setHydrated(true);\n },\n () => {\n if (!cancelled) setHydrated(true);\n },\n );\n return () => {\n cancelled = true;\n };\n }, []);\n\n const commit = (next: Selection): void => {\n dirty.current = true;\n setSelection(next);\n void storeRef.current.save({ version: 1, ...next });\n };\n\n const value: KaleidoscopeStateValue = {\n hydrated,\n presetId: selection.presetId,\n mask: selection.mask,\n patches: selection.patches,\n setPreset: (presetId) => commit({ ...selection, presetId }),\n setMask: (mask) => commit({ ...selection, mask }),\n setPatch: (presetId, patch) =>\n commit({ ...selection, patches: mergePatch(selection.patches, presetId, patch) }),\n // The stored wire shape is book-agnostic; the typed view is recovered at the\n // hook (`useKaleidoscopeState<typeof presets>()`), so the cast is the seam.\n patchesFor: ((presetId: string) =>\n patchListFor(selection.patches, presetId)) as KaleidoscopeStateValue['patchesFor'],\n reset: () => commit({ presetId: null, mask: defaultMask, patches: {} }),\n };\n\n return (\n <KaleidoscopeStateContext.Provider value={value}>{children}</KaleidoscopeStateContext.Provider>\n );\n}\n\n/**\n * The persisted selection plus its setters. Typed by the consumer's book:\n * `useKaleidoscopeState<typeof presets>()`. Throws outside the provider.\n */\nexport function useKaleidoscopeState<\n P extends KaleidoscopePresetBook = KaleidoscopePresetBook,\n>(): KaleidoscopeStateValue<P> {\n const value = useContext(KaleidoscopeStateContext);\n if (value === null) {\n throw new Error(\n 'useKaleidoscopeState: no <KaleidoscopeStateProvider> above this component. ' +\n 'Wrap your app (or the screen using the picker) in the provider from ' +\n \"'react-native-webrtc-kaleidoscope/persistence'.\",\n );\n }\n // Safe by construction: the provider pruned ids against the same book the\n // consumer parameterizes with; the runtime shapes are identical.\n return value as unknown as KaleidoscopeStateValue<P>;\n}\n"]}