octane 0.13.6 → 0.13.7

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.
@@ -9884,6 +9884,7 @@ function initializeContext(ctx, defaultValue) {
9884
9884
  ctx.$$kind = import_runtime_tags.CONTEXT_TAG;
9885
9885
  ctx.defaultValue = defaultValue;
9886
9886
  ctx.$$version = 0;
9887
+ ctx.$$providerVersion = 0;
9887
9888
  (0, import_context_identity.registerContext)(ctx);
9888
9889
  if (__octaneDev) {
9889
9890
  (0, import_context_identity.defineRemovedContextMembers)(ctx);
@@ -9958,6 +9959,7 @@ function provideContext(scope, context, value) {
9958
9959
  else values.delete(context);
9959
9960
  });
9960
9961
  if (had) {
9962
+ context.$$providerVersion = context.$$providerVersion === context.$$version ? context.$$version + 1 : -1;
9961
9963
  context.$$version++;
9962
9964
  (0, import_context_epoch.bumpContextEpoch)();
9963
9965
  }
@@ -11472,12 +11474,29 @@ function promoteMemoAncestry(scope) {
11472
11474
  function recordContextDependency(block, context) {
11473
11475
  if (block === null || !block.memoInChain) return;
11474
11476
  SKIPPED_ROW_REFRESH = refreshSkippedRows;
11475
- (block.$$ctxDirect ??= /* @__PURE__ */ new Map()).set(context, context.$$version);
11477
+ const direct = block.$$ctxDirect ??= /* @__PURE__ */ new Map();
11478
+ const previous = direct.get(context);
11479
+ direct.set(context, context.$$version);
11476
11480
  for (let current = block; current !== null; current = current.parentBlock) {
11477
11481
  if (current.body?.__memo === true || current.$$implicitBail === true) {
11478
11482
  (current.$$ctxReads ??= /* @__PURE__ */ new Map()).set(context, context.$$version);
11479
11483
  }
11480
11484
  }
11485
+ return previous;
11486
+ }
11487
+ function recordLocalContextValue(reader, block, context, value, previous) {
11488
+ if (reader === null || block === null || !block.memoInChain) return;
11489
+ if (typeof previous === "number" || context.$$providerVersion !== context.$$version) return;
11490
+ const cache = reader.$$ctxCache;
11491
+ const owner = cache === context ? reader.$$ctxCacheOwner : cache !== null && reader.$$ctxCacheOwner === null ? cache.get(context) : void 0;
11492
+ if (owner === null || owner === void 0 || owner === DEFAULT_CTX) return;
11493
+ if (previous !== void 0) {
11494
+ if (previous.owner !== owner || !Object.is(previous.value, value)) return;
11495
+ previous.version = context.$$version;
11496
+ block.$$ctxDirect.set(context, previous);
11497
+ } else {
11498
+ block.$$ctxDirect.set(context, { version: context.$$version, owner, value });
11499
+ }
11481
11500
  }
11482
11501
  let SCOPED_READ_TRACKING = false;
11483
11502
  let SCOPED_READS = null;
@@ -11635,8 +11654,10 @@ function readContextFrom(reader, block, context) {
11635
11654
  return context.defaultValue;
11636
11655
  }
11637
11656
  function readContextFromScope(scope, context) {
11638
- recordContextDependency(scope.block, context);
11639
- return readContextFrom(scope, scope.block, context);
11657
+ const previous = recordContextDependency(scope.block, context);
11658
+ const value = readContextFrom(scope, scope.block, context);
11659
+ recordLocalContextValue(scope, scope.block, context, value, previous);
11660
+ return value;
11640
11661
  }
11641
11662
  function getRendererOwnerVisibility(scope) {
11642
11663
  if (findHiddenActivity(scope.block) !== null) return "activity";
@@ -11654,8 +11675,10 @@ function reportRendererOwnerError(scope, error) {
11654
11675
  }
11655
11676
  }
11656
11677
  function useContextInternal(context) {
11657
- recordContextDependency(CURRENT_BLOCK, context);
11658
- return readContextFrom(CURRENT_SCOPE, CURRENT_BLOCK, context);
11678
+ const previous = recordContextDependency(CURRENT_BLOCK, context);
11679
+ const value = readContextFrom(CURRENT_SCOPE, CURRENT_BLOCK, context);
11680
+ recordLocalContextValue(CURRENT_SCOPE, CURRENT_BLOCK, context, value, previous);
11681
+ return value;
11659
11682
  }
11660
11683
  class SuspenseException {
11661
11684
  constructor(thenable) {
@@ -12685,7 +12708,13 @@ function parseTemplate(html, ns, frag) {
12685
12708
  initDomOperations();
12686
12709
  const t = (STAGED_DOM ? STAGED_DOM.view(document) : document).createElement("template");
12687
12710
  if (ns === 0) {
12688
- (STAGED_DOM ? STAGED_DOM.view(t) : t).innerHTML = frag ? `<octane-frag>${html}</octane-frag>` : html;
12711
+ (STAGED_DOM ? STAGED_DOM.view(t) : t).innerHTML = html;
12712
+ if (frag) {
12713
+ const owner = t.content.ownerDocument;
12714
+ const wrapper = (STAGED_DOM ? STAGED_DOM.view(owner) : owner).createElement("octane-frag");
12715
+ (STAGED_DOM ? STAGED_DOM.view(wrapper) : wrapper).appendChild(t.content);
12716
+ return fragmentTemplate(wrapper);
12717
+ }
12689
12718
  const root = getFirstChild(t.content);
12690
12719
  if (root.nodeType === 1 && root.localName === "octane-frag") {
12691
12720
  return fragmentTemplate(root);
@@ -25039,8 +25068,12 @@ function ctxDepsChanged(block) {
25039
25068
  function ctxDirectChanged(block) {
25040
25069
  const direct = block.$$ctxDirect;
25041
25070
  if (direct === null) return false;
25042
- for (const [ctx, version] of direct) {
25043
- if (ctx.$$version !== version) return true;
25071
+ for (const [ctx, read] of direct) {
25072
+ const version = typeof read === "number" ? read : read.version;
25073
+ if (ctx.$$version === version) continue;
25074
+ if (typeof read === "number" || ctx.$$providerVersion !== ctx.$$version || !Object.is(read.owner.$$ctxValues?.get(ctx), read.value))
25075
+ return true;
25076
+ read.version = ctx.$$version;
25044
25077
  }
25045
25078
  return false;
25046
25079
  }
@@ -25109,7 +25142,8 @@ function restampCtxDeps(block) {
25109
25142
  }
25110
25143
  }
25111
25144
  if (hasDirect) {
25112
- for (const [ctx, v] of direct) {
25145
+ for (const [ctx, read] of direct) {
25146
+ const v = typeof read === "number" ? read : read.version;
25113
25147
  const cur = m.get(ctx);
25114
25148
  if (cur !== v) {
25115
25149
  const live = ctx.$$version;
@@ -21,7 +21,7 @@ __export(version_exports, {
21
21
  version: () => version
22
22
  });
23
23
  module.exports = __toCommonJS(version_exports);
24
- const version = "0.13.6";
24
+ const version = "0.13.7";
25
25
  // Annotate the CommonJS export names for ESM import in node:
26
26
  0 && (module.exports = {
27
27
  version
@@ -26202,14 +26202,6 @@ function normalizeAuthoredJsxLiterals(ast) {
26202
26202
  if (typeof raw === 'string' && raw.slice(1, -1) === value) return null;
26203
26203
  return { ...node, value: { ...node.value, raw: JSON.stringify(value) } };
26204
26204
  }
26205
- if (
26206
- node.type === 'JSXStyleElement' &&
26207
- (hasJsxAttribute(node, 'dangerouslySetInnerHTML') || hasJsxAttribute(node, 'children'))
26208
- ) {
26209
- return normalizeAuthoredJsxLiterals(
26210
- b.jsx_element(node, node.openingElement.attributes || [], []),
26211
- );
26212
- }
26213
26205
  return null;
26214
26206
  });
26215
26207
  }
@@ -1,6 +1,6 @@
1
1
  /** @import { Program } from 'estree' */
2
2
 
3
- import { isLayoutWhitespace } from '@tsrx/core';
3
+ import { builders as b, isLayoutWhitespace } from '@tsrx/core';
4
4
 
5
5
  // `@tsrx/core` 0.5 gives template markup TSX's exact tree: the indentation
6
6
  // between children is a `JSXText` of its own, and an `@case`/`@default` arm is
@@ -62,6 +62,18 @@ function adopt(value, editor) {
62
62
  }
63
63
  return out ?? value;
64
64
  }
65
+ // Explicit content props make <style> an ordinary host, not scoped CSS.
66
+ // Normalize before both runtime and editor analysis, retaining authored locations.
67
+ if (
68
+ value.type === 'JSXStyleElement' &&
69
+ value.openingElement.attributes.some(
70
+ (attr) =>
71
+ attr.type === 'JSXAttribute' &&
72
+ (attr.name.name === 'children' || attr.name.name === 'dangerouslySetInnerHTML'),
73
+ )
74
+ ) {
75
+ return adopt(b.jsx_element(value, value.openingElement.attributes, []), editor);
76
+ }
65
77
  /** @type {any} */
66
78
  let out = null;
67
79
  for (const key in value) {
@@ -9769,6 +9769,7 @@ function initializeContext(ctx, defaultValue) {
9769
9769
  ctx.$$kind = CONTEXT_TAG;
9770
9770
  ctx.defaultValue = defaultValue;
9771
9771
  ctx.$$version = 0;
9772
+ ctx.$$providerVersion = 0;
9772
9773
  registerContext(ctx);
9773
9774
  if (__octaneDev) {
9774
9775
  defineRemovedContextMembers(ctx);
@@ -9843,6 +9844,7 @@ function provideContext(scope, context, value) {
9843
9844
  else values.delete(context);
9844
9845
  });
9845
9846
  if (had) {
9847
+ context.$$providerVersion = context.$$providerVersion === context.$$version ? context.$$version + 1 : -1;
9846
9848
  context.$$version++;
9847
9849
  bumpContextEpoch();
9848
9850
  }
@@ -11357,12 +11359,29 @@ function promoteMemoAncestry(scope) {
11357
11359
  function recordContextDependency(block, context) {
11358
11360
  if (block === null || !block.memoInChain) return;
11359
11361
  SKIPPED_ROW_REFRESH = refreshSkippedRows;
11360
- (block.$$ctxDirect ??= /* @__PURE__ */ new Map()).set(context, context.$$version);
11362
+ const direct = block.$$ctxDirect ??= /* @__PURE__ */ new Map();
11363
+ const previous = direct.get(context);
11364
+ direct.set(context, context.$$version);
11361
11365
  for (let current = block; current !== null; current = current.parentBlock) {
11362
11366
  if (current.body?.__memo === true || current.$$implicitBail === true) {
11363
11367
  (current.$$ctxReads ??= /* @__PURE__ */ new Map()).set(context, context.$$version);
11364
11368
  }
11365
11369
  }
11370
+ return previous;
11371
+ }
11372
+ function recordLocalContextValue(reader, block, context, value, previous) {
11373
+ if (reader === null || block === null || !block.memoInChain) return;
11374
+ if (typeof previous === "number" || context.$$providerVersion !== context.$$version) return;
11375
+ const cache = reader.$$ctxCache;
11376
+ const owner = cache === context ? reader.$$ctxCacheOwner : cache !== null && reader.$$ctxCacheOwner === null ? cache.get(context) : void 0;
11377
+ if (owner === null || owner === void 0 || owner === DEFAULT_CTX) return;
11378
+ if (previous !== void 0) {
11379
+ if (previous.owner !== owner || !Object.is(previous.value, value)) return;
11380
+ previous.version = context.$$version;
11381
+ block.$$ctxDirect.set(context, previous);
11382
+ } else {
11383
+ block.$$ctxDirect.set(context, { version: context.$$version, owner, value });
11384
+ }
11366
11385
  }
11367
11386
  let SCOPED_READ_TRACKING = false;
11368
11387
  let SCOPED_READS = null;
@@ -11520,8 +11539,10 @@ function readContextFrom(reader, block, context) {
11520
11539
  return context.defaultValue;
11521
11540
  }
11522
11541
  function readContextFromScope(scope, context) {
11523
- recordContextDependency(scope.block, context);
11524
- return readContextFrom(scope, scope.block, context);
11542
+ const previous = recordContextDependency(scope.block, context);
11543
+ const value = readContextFrom(scope, scope.block, context);
11544
+ recordLocalContextValue(scope, scope.block, context, value, previous);
11545
+ return value;
11525
11546
  }
11526
11547
  function getRendererOwnerVisibility(scope) {
11527
11548
  if (findHiddenActivity(scope.block) !== null) return "activity";
@@ -11539,8 +11560,10 @@ function reportRendererOwnerError(scope, error) {
11539
11560
  }
11540
11561
  }
11541
11562
  function useContextInternal(context) {
11542
- recordContextDependency(CURRENT_BLOCK, context);
11543
- return readContextFrom(CURRENT_SCOPE, CURRENT_BLOCK, context);
11563
+ const previous = recordContextDependency(CURRENT_BLOCK, context);
11564
+ const value = readContextFrom(CURRENT_SCOPE, CURRENT_BLOCK, context);
11565
+ recordLocalContextValue(CURRENT_SCOPE, CURRENT_BLOCK, context, value, previous);
11566
+ return value;
11544
11567
  }
11545
11568
  class SuspenseException {
11546
11569
  constructor(thenable) {
@@ -12570,7 +12593,13 @@ function parseTemplate(html, ns, frag) {
12570
12593
  initDomOperations();
12571
12594
  const t = (STAGED_DOM ? STAGED_DOM.view(document) : document).createElement("template");
12572
12595
  if (ns === 0) {
12573
- (STAGED_DOM ? STAGED_DOM.view(t) : t).innerHTML = frag ? `<octane-frag>${html}</octane-frag>` : html;
12596
+ (STAGED_DOM ? STAGED_DOM.view(t) : t).innerHTML = html;
12597
+ if (frag) {
12598
+ const owner = t.content.ownerDocument;
12599
+ const wrapper = (STAGED_DOM ? STAGED_DOM.view(owner) : owner).createElement("octane-frag");
12600
+ (STAGED_DOM ? STAGED_DOM.view(wrapper) : wrapper).appendChild(t.content);
12601
+ return fragmentTemplate(wrapper);
12602
+ }
12574
12603
  const root = getFirstChild(t.content);
12575
12604
  if (root.nodeType === 1 && root.localName === "octane-frag") {
12576
12605
  return fragmentTemplate(root);
@@ -24933,8 +24962,12 @@ function ctxDepsChanged(block) {
24933
24962
  function ctxDirectChanged(block) {
24934
24963
  const direct = block.$$ctxDirect;
24935
24964
  if (direct === null) return false;
24936
- for (const [ctx, version] of direct) {
24937
- if (ctx.$$version !== version) return true;
24965
+ for (const [ctx, read] of direct) {
24966
+ const version = typeof read === "number" ? read : read.version;
24967
+ if (ctx.$$version === version) continue;
24968
+ if (typeof read === "number" || ctx.$$providerVersion !== ctx.$$version || !Object.is(read.owner.$$ctxValues?.get(ctx), read.value))
24969
+ return true;
24970
+ read.version = ctx.$$version;
24938
24971
  }
24939
24972
  return false;
24940
24973
  }
@@ -25003,7 +25036,8 @@ function restampCtxDeps(block) {
25003
25036
  }
25004
25037
  }
25005
25038
  if (hasDirect) {
25006
- for (const [ctx, v] of direct) {
25039
+ for (const [ctx, read] of direct) {
25040
+ const v = typeof read === "number" ? read : read.version;
25007
25041
  const cur = m.get(ctx);
25008
25042
  if (cur !== v) {
25009
25043
  const live = ctx.$$version;
@@ -1,4 +1,4 @@
1
- const version = "0.13.6";
1
+ const version = "0.13.7";
2
2
  export {
3
3
  version
4
4
  };
package/dist/runtime.d.ts CHANGED
@@ -365,7 +365,7 @@ export interface Block extends Scope {
365
365
  */
366
366
  inactive: boolean;
367
367
  /** Direct (own) context reads this render — drives memo invalidation alongside $$ctxReads. */
368
- $$ctxDirect: Map<Context<any>, any> | null;
368
+ $$ctxDirect: Map<Context<any>, DirectContextRead> | null;
369
369
  /**
370
370
  * The context epoch at which this block's $$ctxReads/$$ctxDirect
371
371
  * entries were last known consistent with live context versions: sampled when
@@ -1074,6 +1074,8 @@ export interface Context<T> {
1074
1074
  * changed", so an unpaired bump strands consumers.
1075
1075
  */
1076
1076
  $$version: number;
1077
+ /** Local-provider provenance; -1 after any external version write. */
1078
+ $$providerVersion?: number;
1077
1079
  }
1078
1080
  /**
1079
1081
  * Create a Context. Providers push the value into a Block-scoped slot; `use(ctx)`
@@ -1261,6 +1263,11 @@ interface HostContextRequestSignal {
1261
1263
  * @internal owner-bridge ABI (octane/react and future hosts).
1262
1264
  */
1263
1265
  export declare function createHostContextRequest(thenable: PromiseLike<unknown>): HostContextRequestSignal;
1266
+ type DirectContextRead = number | {
1267
+ version: number;
1268
+ owner: Scope;
1269
+ value: unknown;
1270
+ };
1264
1271
  /** @internal Live context reader rooted at a captured DOM boundary scope. */
1265
1272
  export declare function readContextFromScope<T>(scope: Scope, context: Context<T>): T;
1266
1273
  /** @internal Cold adapter query; native render and hide paths pay no extra work. */
package/dist/runtime.js CHANGED
@@ -9768,6 +9768,7 @@ function initializeContext(ctx, defaultValue) {
9768
9768
  ctx.$$kind = CONTEXT_TAG;
9769
9769
  ctx.defaultValue = defaultValue;
9770
9770
  ctx.$$version = 0;
9771
+ ctx.$$providerVersion = 0;
9771
9772
  registerContext(ctx);
9772
9773
  if (process.env.NODE_ENV !== "production") {
9773
9774
  defineRemovedContextMembers(ctx);
@@ -9842,6 +9843,7 @@ function provideContext(scope, context, value) {
9842
9843
  else values.delete(context);
9843
9844
  });
9844
9845
  if (had) {
9846
+ context.$$providerVersion = context.$$providerVersion === context.$$version ? context.$$version + 1 : -1;
9845
9847
  context.$$version++;
9846
9848
  bumpContextEpoch();
9847
9849
  }
@@ -11356,12 +11358,29 @@ function promoteMemoAncestry(scope) {
11356
11358
  function recordContextDependency(block, context) {
11357
11359
  if (block === null || !block.memoInChain) return;
11358
11360
  SKIPPED_ROW_REFRESH = refreshSkippedRows;
11359
- (block.$$ctxDirect ??= /* @__PURE__ */ new Map()).set(context, context.$$version);
11361
+ const direct = block.$$ctxDirect ??= /* @__PURE__ */ new Map();
11362
+ const previous = direct.get(context);
11363
+ direct.set(context, context.$$version);
11360
11364
  for (let current = block; current !== null; current = current.parentBlock) {
11361
11365
  if (current.body?.__memo === true || current.$$implicitBail === true) {
11362
11366
  (current.$$ctxReads ??= /* @__PURE__ */ new Map()).set(context, context.$$version);
11363
11367
  }
11364
11368
  }
11369
+ return previous;
11370
+ }
11371
+ function recordLocalContextValue(reader, block, context, value, previous) {
11372
+ if (reader === null || block === null || !block.memoInChain) return;
11373
+ if (typeof previous === "number" || context.$$providerVersion !== context.$$version) return;
11374
+ const cache = reader.$$ctxCache;
11375
+ const owner = cache === context ? reader.$$ctxCacheOwner : cache !== null && reader.$$ctxCacheOwner === null ? cache.get(context) : void 0;
11376
+ if (owner === null || owner === void 0 || owner === DEFAULT_CTX) return;
11377
+ if (previous !== void 0) {
11378
+ if (previous.owner !== owner || !Object.is(previous.value, value)) return;
11379
+ previous.version = context.$$version;
11380
+ block.$$ctxDirect.set(context, previous);
11381
+ } else {
11382
+ block.$$ctxDirect.set(context, { version: context.$$version, owner, value });
11383
+ }
11365
11384
  }
11366
11385
  let SCOPED_READ_TRACKING = false;
11367
11386
  let SCOPED_READS = null;
@@ -11519,8 +11538,10 @@ function readContextFrom(reader, block, context) {
11519
11538
  return context.defaultValue;
11520
11539
  }
11521
11540
  function readContextFromScope(scope, context) {
11522
- recordContextDependency(scope.block, context);
11523
- return readContextFrom(scope, scope.block, context);
11541
+ const previous = recordContextDependency(scope.block, context);
11542
+ const value = readContextFrom(scope, scope.block, context);
11543
+ recordLocalContextValue(scope, scope.block, context, value, previous);
11544
+ return value;
11524
11545
  }
11525
11546
  function getRendererOwnerVisibility(scope) {
11526
11547
  if (findHiddenActivity(scope.block) !== null) return "activity";
@@ -11538,8 +11559,10 @@ function reportRendererOwnerError(scope, error) {
11538
11559
  }
11539
11560
  }
11540
11561
  function useContextInternal(context) {
11541
- recordContextDependency(CURRENT_BLOCK, context);
11542
- return readContextFrom(CURRENT_SCOPE, CURRENT_BLOCK, context);
11562
+ const previous = recordContextDependency(CURRENT_BLOCK, context);
11563
+ const value = readContextFrom(CURRENT_SCOPE, CURRENT_BLOCK, context);
11564
+ recordLocalContextValue(CURRENT_SCOPE, CURRENT_BLOCK, context, value, previous);
11565
+ return value;
11543
11566
  }
11544
11567
  class SuspenseException {
11545
11568
  constructor(thenable) {
@@ -12569,7 +12592,13 @@ function parseTemplate(html, ns, frag) {
12569
12592
  initDomOperations();
12570
12593
  const t = (STAGED_DOM ? STAGED_DOM.view(document) : document).createElement("template");
12571
12594
  if (ns === 0) {
12572
- (STAGED_DOM ? STAGED_DOM.view(t) : t).innerHTML = frag ? `<octane-frag>${html}</octane-frag>` : html;
12595
+ (STAGED_DOM ? STAGED_DOM.view(t) : t).innerHTML = html;
12596
+ if (frag) {
12597
+ const owner = t.content.ownerDocument;
12598
+ const wrapper = (STAGED_DOM ? STAGED_DOM.view(owner) : owner).createElement("octane-frag");
12599
+ (STAGED_DOM ? STAGED_DOM.view(wrapper) : wrapper).appendChild(t.content);
12600
+ return fragmentTemplate(wrapper);
12601
+ }
12573
12602
  const root = getFirstChild(t.content);
12574
12603
  if (root.nodeType === 1 && root.localName === "octane-frag") {
12575
12604
  return fragmentTemplate(root);
@@ -24932,8 +24961,12 @@ function ctxDepsChanged(block) {
24932
24961
  function ctxDirectChanged(block) {
24933
24962
  const direct = block.$$ctxDirect;
24934
24963
  if (direct === null) return false;
24935
- for (const [ctx, version] of direct) {
24936
- if (ctx.$$version !== version) return true;
24964
+ for (const [ctx, read] of direct) {
24965
+ const version = typeof read === "number" ? read : read.version;
24966
+ if (ctx.$$version === version) continue;
24967
+ if (typeof read === "number" || ctx.$$providerVersion !== ctx.$$version || !Object.is(read.owner.$$ctxValues?.get(ctx), read.value))
24968
+ return true;
24969
+ read.version = ctx.$$version;
24937
24970
  }
24938
24971
  return false;
24939
24972
  }
@@ -25002,7 +25035,8 @@ function restampCtxDeps(block) {
25002
25035
  }
25003
25036
  }
25004
25037
  if (hasDirect) {
25005
- for (const [ctx, v] of direct) {
25038
+ for (const [ctx, read] of direct) {
25039
+ const v = typeof read === "number" ? read : read.version;
25006
25040
  const cur = m.get(ctx);
25007
25041
  if (cur !== v) {
25008
25042
  const live = ctx.$$version;
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.13.6";
1
+ const version = "0.13.7";
2
2
  export {
3
3
  version
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octane",
3
- "version": "0.13.6",
3
+ "version": "0.13.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": [