@lumencast/runtime 0.6.0 → 0.8.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 (133) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/broadcast-Gcd-dmC7.js +12 -0
  3. package/dist/broadcast-Gcd-dmC7.js.map +1 -0
  4. package/dist/control-C5TfClga.js +17 -0
  5. package/dist/control-C5TfClga.js.map +1 -0
  6. package/dist/{index-Crkij3C4.js → index-N-VqrIxN.js} +305 -210
  7. package/dist/index-N-VqrIxN.js.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.html +1 -1
  11. package/dist/index.js +13 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/lumencast.js +14 -9
  14. package/dist/modes/broadcast.d.ts.map +1 -1
  15. package/dist/modes/broadcast.js +6 -1
  16. package/dist/modes/broadcast.js.map +1 -1
  17. package/dist/modes/control.d.ts.map +1 -1
  18. package/dist/modes/control.js +6 -1
  19. package/dist/modes/control.js.map +1 -1
  20. package/dist/modes/test.d.ts.map +1 -1
  21. package/dist/modes/test.js +2 -1
  22. package/dist/modes/test.js.map +1 -1
  23. package/dist/render/allowed-hosts.d.ts +41 -0
  24. package/dist/render/allowed-hosts.d.ts.map +1 -0
  25. package/dist/render/allowed-hosts.js +88 -0
  26. package/dist/render/allowed-hosts.js.map +1 -0
  27. package/dist/render/asset-resolve.d.ts +27 -0
  28. package/dist/render/asset-resolve.d.ts.map +1 -0
  29. package/dist/render/asset-resolve.js +86 -0
  30. package/dist/render/asset-resolve.js.map +1 -0
  31. package/dist/render/blend-mode.d.ts +7 -0
  32. package/dist/render/blend-mode.d.ts.map +1 -0
  33. package/dist/render/blend-mode.js +49 -0
  34. package/dist/render/blend-mode.js.map +1 -0
  35. package/dist/render/bundle.d.ts +9 -1
  36. package/dist/render/bundle.d.ts.map +1 -1
  37. package/dist/render/bundle.js.map +1 -1
  38. package/dist/render/fill.d.ts +36 -3
  39. package/dist/render/fill.d.ts.map +1 -1
  40. package/dist/render/fill.js +222 -23
  41. package/dist/render/fill.js.map +1 -1
  42. package/dist/render/headless.d.ts +39 -0
  43. package/dist/render/headless.d.ts.map +1 -0
  44. package/dist/render/headless.js +83 -0
  45. package/dist/render/headless.js.map +1 -0
  46. package/dist/render/mask.d.ts +87 -0
  47. package/dist/render/mask.d.ts.map +1 -0
  48. package/dist/render/mask.js +243 -0
  49. package/dist/render/mask.js.map +1 -0
  50. package/dist/render/primitives/frame.d.ts.map +1 -1
  51. package/dist/render/primitives/frame.js +91 -5
  52. package/dist/render/primitives/frame.js.map +1 -1
  53. package/dist/render/primitives/grid.d.ts +1 -1
  54. package/dist/render/primitives/grid.d.ts.map +1 -1
  55. package/dist/render/primitives/grid.js +4 -1
  56. package/dist/render/primitives/grid.js.map +1 -1
  57. package/dist/render/primitives/image.d.ts +8 -1
  58. package/dist/render/primitives/image.d.ts.map +1 -1
  59. package/dist/render/primitives/image.js +17 -3
  60. package/dist/render/primitives/image.js.map +1 -1
  61. package/dist/render/primitives/index.d.ts +7 -0
  62. package/dist/render/primitives/index.d.ts.map +1 -1
  63. package/dist/render/primitives/index.js.map +1 -1
  64. package/dist/render/primitives/media.d.ts +11 -2
  65. package/dist/render/primitives/media.d.ts.map +1 -1
  66. package/dist/render/primitives/media.js +14 -3
  67. package/dist/render/primitives/media.js.map +1 -1
  68. package/dist/render/primitives/shape.d.ts.map +1 -1
  69. package/dist/render/primitives/shape.js +29 -26
  70. package/dist/render/primitives/shape.js.map +1 -1
  71. package/dist/render/primitives/stack.d.ts +1 -1
  72. package/dist/render/primitives/stack.d.ts.map +1 -1
  73. package/dist/render/primitives/stack.js +5 -1
  74. package/dist/render/primitives/stack.js.map +1 -1
  75. package/dist/render/primitives/text.d.ts.map +1 -1
  76. package/dist/render/primitives/text.js +0 -1
  77. package/dist/render/primitives/text.js.map +1 -1
  78. package/dist/render/prop-allowlist.d.ts.map +1 -1
  79. package/dist/render/prop-allowlist.js +25 -2
  80. package/dist/render/prop-allowlist.js.map +1 -1
  81. package/dist/render/shape-geometry.d.ts +81 -0
  82. package/dist/render/shape-geometry.d.ts.map +1 -0
  83. package/dist/render/shape-geometry.js +199 -0
  84. package/dist/render/shape-geometry.js.map +1 -0
  85. package/dist/render/shape-index.d.ts +28 -0
  86. package/dist/render/shape-index.d.ts.map +1 -0
  87. package/dist/render/shape-index.js +77 -0
  88. package/dist/render/shape-index.js.map +1 -0
  89. package/dist/render/tree.d.ts.map +1 -1
  90. package/dist/render/tree.js +175 -3
  91. package/dist/render/tree.js.map +1 -1
  92. package/dist/render/universal-wrapper.d.ts +27 -1
  93. package/dist/render/universal-wrapper.d.ts.map +1 -1
  94. package/dist/render/universal-wrapper.js +98 -22
  95. package/dist/render/universal-wrapper.js.map +1 -1
  96. package/dist/{status-pill-BT5b-yET.js → status-pill-BaLQoIDl.js} +2 -2
  97. package/dist/{status-pill-BT5b-yET.js.map → status-pill-BaLQoIDl.js.map} +1 -1
  98. package/dist/{test-_hh1JvAd.js → test-CA30C2By.js} +51 -51
  99. package/dist/{test-_hh1JvAd.js.map → test-CA30C2By.js.map} +1 -1
  100. package/dist/tree-1coZ32nd.js +1777 -0
  101. package/dist/tree-1coZ32nd.js.map +1 -0
  102. package/package.json +6 -5
  103. package/src/index.ts +24 -0
  104. package/src/modes/broadcast.tsx +12 -1
  105. package/src/modes/control.tsx +10 -1
  106. package/src/modes/test.tsx +4 -1
  107. package/src/render/allowed-hosts.tsx +100 -0
  108. package/src/render/asset-resolve.ts +97 -0
  109. package/src/render/blend-mode.ts +50 -0
  110. package/src/render/bundle.ts +6 -1
  111. package/src/render/fill.tsx +266 -24
  112. package/src/render/headless.tsx +129 -0
  113. package/src/render/mask.tsx +389 -0
  114. package/src/render/primitives/frame.tsx +101 -5
  115. package/src/render/primitives/grid.tsx +4 -1
  116. package/src/render/primitives/image.tsx +17 -3
  117. package/src/render/primitives/index.ts +7 -0
  118. package/src/render/primitives/media.tsx +14 -3
  119. package/src/render/primitives/shape.tsx +39 -75
  120. package/src/render/primitives/stack.tsx +5 -1
  121. package/src/render/primitives/text.tsx +0 -1
  122. package/src/render/prop-allowlist.ts +25 -2
  123. package/src/render/shape-geometry.tsx +315 -0
  124. package/src/render/shape-index.tsx +90 -0
  125. package/src/render/tree.tsx +214 -12
  126. package/src/render/universal-wrapper.tsx +128 -21
  127. package/dist/broadcast-DO7jEkix.js +0 -11
  128. package/dist/broadcast-DO7jEkix.js.map +0 -1
  129. package/dist/control-BSfl4_cO.js +0 -16
  130. package/dist/control-BSfl4_cO.js.map +0 -1
  131. package/dist/index-Crkij3C4.js.map +0 -1
  132. package/dist/tree-DBj9SJgs.js +0 -1230
  133. package/dist/tree-DBj9SJgs.js.map +0 -1
@@ -0,0 +1,243 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { checkHostAllowed } from "@lumencast/protocol";
3
+ import { emitDiagnostic } from "./diagnostics";
4
+ /** Closed `mask.type` allowlist — runtime half of the double-gate (T4).
5
+ * Mirrors `@lumencast/compiler` `MASK_TYPES` ; kept local so the runtime
6
+ * has no compile-time dependency on the compiler package. */
7
+ const MASK_TYPES = new Set(["alpha", "luminance"]);
8
+ /** Feather pad (px). The group/shape mask wrapper's `overflow:hidden` box is
9
+ * grown by this on every side (tree.tsx, `inset:-PAD`) and the coverage is
10
+ * shifted back by the same amount (buildMask) so a BLURRED mask rim isn't
11
+ * re-cut into a hard square at the box edge. Generous enough for the
12
+ * bg-texture ellipse's ~3σ (53.88 CSS sigma) feather. Sharp masks are
13
+ * unaffected (their alpha-0 region simply sits inside the grown box). */
14
+ export const MASK_FEATHER_PAD = 180;
15
+ /** Closed `mask.op` allowlist — runtime half of the double-gate (T4). */
16
+ const MASK_OPS = new Set(["intersect", "subtract", "union"]);
17
+ let maskIdSeq = 0;
18
+ function nextMaskId() {
19
+ maskIdSeq = (maskIdSeq + 1) % 1_000_000;
20
+ return `lumen-mask-${maskIdSeq.toString(36)}`;
21
+ }
22
+ /** Sanitise a shape `ref` to a safe SVG id token. A legitimate ref is a
23
+ * compiler-assigned node id (`[A-Za-z0-9_:-]`). Anything carrying markup
24
+ * characters (`<`, `"`, `#`, whitespace, `(`) is rejected — it can only be
25
+ * an injection attempt, and there is no legitimate id that needs them. */
26
+ function safeIdRef(ref) {
27
+ return /^[A-Za-z0-9_:-]+$/.test(ref) ? ref : null;
28
+ }
29
+ function finite(v) {
30
+ return typeof v === "number" && Number.isFinite(v);
31
+ }
32
+ /**
33
+ * Validate a loose `mask` value into a strict {@link MaskSpec}, or `null`.
34
+ * Re-runs the closed-enum gates (T4) and the source-shape discriminant ;
35
+ * a malformed mask is dropped whole (it cannot be partially honoured).
36
+ */
37
+ export function parseMaskSpec(value, nodeId) {
38
+ if (typeof value !== "object" || value === null)
39
+ return null;
40
+ const m = value;
41
+ if (typeof m.type !== "string" || !MASK_TYPES.has(m.type)) {
42
+ emitDiagnostic(nodeId, "mask.type", "is not alpha|luminance ; mask omitted (ADR 002 §3.2, T4)");
43
+ return null;
44
+ }
45
+ if (typeof m.op !== "string" || !MASK_OPS.has(m.op)) {
46
+ emitDiagnostic(nodeId, "mask.op", "is not intersect|subtract|union ; mask omitted (ADR 002 §3.2, T4)");
47
+ return null;
48
+ }
49
+ const src = m.source;
50
+ if (typeof src !== "object" || src === null) {
51
+ emitDiagnostic(nodeId, "mask.source", "is not a typed shape|image source ; mask omitted (T3)");
52
+ return null;
53
+ }
54
+ const s = src;
55
+ let source;
56
+ if (s.kind === "shape" && typeof s.ref === "string") {
57
+ source = { kind: "shape", ref: s.ref };
58
+ }
59
+ else if (s.kind === "image" && typeof s.src === "string") {
60
+ // Preserve the mask source's box (`srcRect`: offset from THIS node + size)
61
+ // when present — it places/sizes the CSS mask to the source raster, shared
62
+ // across siblings of different boxes (the caramel halo + drift fix).
63
+ const sr = s.srcRect;
64
+ source =
65
+ sr && finite(sr.x) && finite(sr.y) && finite(sr.w) && finite(sr.h)
66
+ ? { kind: "image", src: s.src, srcRect: { x: sr.x, y: sr.y, w: sr.w, h: sr.h } }
67
+ : { kind: "image", src: s.src };
68
+ }
69
+ else if (s.kind === "group" && typeof s.ref === "string") {
70
+ source = { kind: "group", ref: s.ref };
71
+ }
72
+ else {
73
+ emitDiagnostic(nodeId, "mask.source", "is not a typed shape|image|group source ; mask omitted (T3)");
74
+ return null;
75
+ }
76
+ const spec = { source, type: m.type, op: m.op };
77
+ const pos = m.position;
78
+ if (pos && finite(pos.x) && finite(pos.y))
79
+ spec.position = { x: pos.x, y: pos.y };
80
+ const size = m.size;
81
+ if (size && finite(size.w) && finite(size.h))
82
+ spec.size = { w: size.w, h: size.h };
83
+ return spec;
84
+ }
85
+ /**
86
+ * Build a `<mask>` element + the CSS reference from a typed mask spec.
87
+ * Returns `null` when the mask must be omitted (bad enum, rejected host,
88
+ * unsafe ref) — the caller renders the subtree unmasked.
89
+ *
90
+ * @param mask the typed spec (already enum-checked by parseMaskSpec,
91
+ * but re-checked here so the builder is safe standalone).
92
+ * @param allowedHosts the bundle's `assets.allowedHosts` ; an image source is
93
+ * gated against it (T1/T2) before reaching `<image href>`.
94
+ * @param nodeId for diagnostics (never carries a value, R9).
95
+ * @param resolveShape resolves a shape `mask.source.ref` to its inlined
96
+ * coverage geometry (#K). Omitted / returns `null` ⇒ the
97
+ * shape source is pending → the whole mask is omitted.
98
+ */
99
+ export function buildMask(mask, allowedHosts, nodeId, resolveShape, boxSize, feather = false) {
100
+ // T4 — defence in depth : re-validate the enums even though parseMaskSpec
101
+ // already did, so `buildMask` is safe to call on any typed input.
102
+ if (!MASK_TYPES.has(mask.type) || !MASK_OPS.has(mask.op)) {
103
+ emitDiagnostic(nodeId, "mask", "type/op outside the closed enum ; mask omitted (T4)");
104
+ return null;
105
+ }
106
+ const id = nextMaskId();
107
+ // The mask content : a single element painted into the mask's luminance
108
+ // (or alpha) channel. Coordinates come from typed numbers only.
109
+ const x = mask.position?.x;
110
+ const y = mask.position?.y;
111
+ const w = mask.size?.w;
112
+ const h = mask.size?.h;
113
+ const geom = {
114
+ ...(finite(x) ? { x } : {}),
115
+ ...(finite(y) ? { y } : {}),
116
+ ...(finite(w) ? { width: w } : {}),
117
+ ...(finite(h) ? { height: h } : {}),
118
+ };
119
+ let content;
120
+ if (mask.source.kind === "image") {
121
+ // T1/T2 — gate the URL BEFORE it reaches the `<image href>`. A rejected
122
+ // host/scheme omits the whole mask with a static-reason diagnostic.
123
+ const decision = checkHostAllowed(mask.source.src, allowedHosts);
124
+ if (!decision.allowed) {
125
+ emitDiagnostic(nodeId, "mask.source.src", `image host/scheme rejected ; mask omitted (T1/T2 — ${decision.reason ?? "denied"})`);
126
+ return null;
127
+ }
128
+ // `href` is a typed attribute on a constructed element — never markup.
129
+ // For an alpha mask, read the source's own alpha (mask-type:alpha on the
130
+ // <mask>) ; luminance is the SVG default. The image fills the masked box
131
+ // when no explicit geometry is given.
132
+ // External <image> in an SVG <mask> (0×0 SVG) never loads. For `intersect`
133
+ // apply the raster directly as a CSS mask-image. The masked image content is
134
+ // drawn with `object-fit: cover` (Figma scaleMode FILL), so the mask raster
135
+ // — the SAME source image — must `cover` too, else a `Wpx Hpx` (stretch)
136
+ // mask clips a differently-scaled crop and the caramel ribbon shrinks /
137
+ // shifts off its wave. `cover` keeps the alpha aligned with the content.
138
+ if (mask.op === "intersect") {
139
+ const mode = mask.type === "alpha" ? "alpha" : "luminance";
140
+ const url = `url("${mask.source.src}")`;
141
+ // Place + size the mask to the SOURCE raster's box (`srcRect`: offset from
142
+ // this node's box top-left + size), shared by every masked sibling — NOT
143
+ // `cover` of each sibling's box (inflates → orange halo) nor centred
144
+ // (drifts → mask pulled down). The caramel gradient (1146) and 3d-render
145
+ // (930) thus clip to the SAME wave at the SAME spot.
146
+ const rect = mask.source
147
+ .srcRect;
148
+ const usable = rect && finite(rect.x) && finite(rect.y) && finite(rect.w) && finite(rect.h);
149
+ const sizeCss = usable ? `${rect.w}px ${rect.h}px` : "cover";
150
+ const posCss = usable ? `${rect.x}px ${rect.y}px` : "center";
151
+ return {
152
+ def: _jsx("defs", {}, id),
153
+ style: {
154
+ maskImage: url,
155
+ WebkitMaskImage: url,
156
+ maskSize: sizeCss,
157
+ WebkitMaskSize: sizeCss,
158
+ maskRepeat: "no-repeat",
159
+ WebkitMaskRepeat: "no-repeat",
160
+ maskPosition: posCss,
161
+ WebkitMaskPosition: posCss,
162
+ maskMode: mode,
163
+ },
164
+ id,
165
+ feather: false,
166
+ };
167
+ }
168
+ const imgGeom = Object.keys(geom).length > 0
169
+ ? geom
170
+ : finite(boxSize?.w) && finite(boxSize?.h)
171
+ ? { x: 0, y: 0, width: boxSize.w, height: boxSize.h }
172
+ : { width: "100%", height: "100%" };
173
+ content = _jsx("image", { href: mask.source.src, preserveAspectRatio: "none", ...imgGeom });
174
+ }
175
+ else {
176
+ // Shape (#K) or group/frame (#O) source — INLINE the referenced node's
177
+ // resolved coverage geometry into the `<mask>`, built element-by-element
178
+ // (T3 : zero markup). For a `shape` the resolver returns its own outline ;
179
+ // for a `group` it returns the composite of the container's visible
180
+ // children (the resolver routes on the referenced node's kind).
181
+ //
182
+ // The ref is first re-sanitised (defence in depth : a live LSDP delta could
183
+ // smuggle markup chars), then resolved against the Tree's referenceable-node
184
+ // index. A PENDING ref (id absent) → the mask is omitted, sub-tree rendered
185
+ // unmasked (A2.1 : omission, not crash). Anti-cycle is enforced by the
186
+ // resolver inlining ONLY geometry — never any node's own mask.
187
+ const safeRef = safeIdRef(mask.source.ref);
188
+ if (safeRef === null) {
189
+ emitDiagnostic(nodeId, "mask.source.ref", "shape ref is not a safe id token ; mask omitted (T3)");
190
+ return null;
191
+ }
192
+ const resolved = resolveShape?.(safeRef) ?? null;
193
+ if (resolved === null) {
194
+ emitDiagnostic(nodeId, "mask.source.ref", "shape ref does not resolve to an indexed shape ; mask omitted (ADR 002 A2.1 #K)");
195
+ return null;
196
+ }
197
+ // Position/size place the inlined geometry numerically when given,
198
+ // wrapping it in a translated group (typed numbers only, never a string).
199
+ content =
200
+ Object.keys(geom).length > 0 ? (_jsx("g", { transform: finite(geom.x) || finite(geom.y)
201
+ ? `translate(${finite(geom.x) ? geom.x : 0} ${finite(geom.y) ? geom.y : 0})`
202
+ : undefined, children: resolved })) : (resolved);
203
+ }
204
+ // Feather pad : the mask wrapper's box is grown by MASK_FEATHER_PAD on every
205
+ // side (tree.tsx, `inset:-PAD`) so a BLURRED coverage edge isn't re-cut into a
206
+ // hard square by the wrapper's `overflow:hidden`. The coverage is shifted back
207
+ // by the SAME amount here (userSpaceOnUse), so the mask stays put while the box
208
+ // grows. A sharp mask is unaffected (its alpha-0 region just sits inside the
209
+ // grown box). Applied to the coverage only — never the full-coverage union/
210
+ // subtract rect, which must keep spanning the whole (grown) box.
211
+ if (feather) {
212
+ content = (_jsx("g", { transform: `translate(${MASK_FEATHER_PAD} ${MASK_FEATHER_PAD})`, children: content }, "feather-pad"));
213
+ }
214
+ // `union` widens coverage : a base full-coverage white rect is unioned with
215
+ // the source paint. `subtract` removes the source area from full coverage by
216
+ // painting the source black over a white base. `intersect` (default) keeps
217
+ // only the source's own coverage. All three are expressed by which fixed
218
+ // elements we emit — never by interpolating an author string.
219
+ let inner;
220
+ if (mask.op === "intersect") {
221
+ inner = content;
222
+ }
223
+ else if (mask.op === "union") {
224
+ inner = (_jsxs(_Fragment, { children: [_jsx("rect", { x: 0, y: 0, width: "100%", height: "100%", fill: "white" }), content] }));
225
+ }
226
+ else {
227
+ // subtract : white base, source painted black to carve it out.
228
+ inner = (_jsxs(_Fragment, { children: [_jsx("rect", { x: 0, y: 0, width: "100%", height: "100%", fill: "white" }), _jsx("g", { style: { filter: "invert(1)" }, children: content })] }));
229
+ }
230
+ const def = (_jsx("mask", { id: id,
231
+ // `maskContentUnits` (not `maskUnits`) places the coverage in the masked
232
+ // element's user space. The mask REGION is widened to −50%..150% of the
233
+ // masked box (objectBoundingBox units) so a FEATHERED coverage (the
234
+ // bg-texture ellipse blurred 107.76) keeps its soft rim — the default
235
+ // −10%..120% clipped the blur to a hard SQUARE edge. (The prior
236
+ // `maskUnits="userSpaceOnUse"` WITHOUT x/y/width/height shrank the region
237
+ // to the 0×0 defs-svg viewport, hiding every group/shape-masked subtree —
238
+ // the platform-wide bug ; an explicit region is the robust form.)
239
+ maskContentUnits: "userSpaceOnUse", x: "-50%", y: "-50%", width: "200%", height: "200%", ...(mask.type === "alpha" && mask.source.kind !== "image" ? { "mask-type": "alpha" } : {}), children: inner }, id));
240
+ const ref = `url(#${id})`;
241
+ return { def, style: { mask: ref, WebkitMask: ref }, id, feather };
242
+ }
243
+ //# sourceMappingURL=mask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mask.js","sourceRoot":"","sources":["../../src/render/mask.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/frame.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAM9C;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,EACpB,QAAQ,EACR,MAAM,EACN,aAAa,EACb,cAAc,EACd,QAAQ,GACT,EAAE,cAAc,2CA2DhB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAKxE"}
1
+ {"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/frame.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAO9C;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,EACpB,QAAQ,EACR,MAAM,EACN,aAAa,EACb,cAAc,EACd,QAAQ,GACT,EAAE,cAAc,2CAsGhB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAKxE"}
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { motion } from "framer-motion";
3
3
  import { toFramer, mountPlay, resolveTransition } from "../../animate/transitions";
4
- import { backgroundsToCss, parseFills } from "../fill";
4
+ import { backgroundsToCss, parseFills, gateImageFills } from "../fill";
5
5
  import { parseCssColor, warnRejectedColor } from "../css-color";
6
6
  import { emitDiagnostic } from "../diagnostics";
7
+ import { useAllowedHosts } from "../allowed-hosts";
7
8
  /** Absolute-positioned container with size + transform + opacity.
8
9
  * Animatable on `transform` and `opacity` only — width/height/position
9
10
  * changes are intentionally *not* animatable to keep the broadcast
@@ -23,7 +24,20 @@ export function Frame({ resolved, nodeId, transitionFor, animateInitial, childre
23
24
  const height = sizeProp(resolved.height);
24
25
  const opacity = numberOr(resolved.opacity, 1);
25
26
  const scale = numberOr(resolved.scale, 1);
26
- const rotate = numberOr(resolved.rotate, 0);
27
+ // Static `rotation` (LSML §5.4) is applied HERE, on the frame's own box, so it
28
+ // pivots around the frame centre (transform-origin: center). It must NOT go on
29
+ // the UniversalWrapper for a frame : the wrapper carries no position/size for
30
+ // a self-positioning frame, so it collapses to a 0-height box and the rotation
31
+ // pivots around the wrong point (the picto/caramel swung off-place). `rotate`
32
+ // (animated) still wins when present.
33
+ const rotate = numberOr(resolved.rotate, numberOr(resolved.rotation, 0));
34
+ // Mirror (Figma `scaleY(-1)`, from a negative transform determinant). Applied
35
+ // on the frame box like the rotation so it composes correctly.
36
+ const flipY = resolved.flipY === true;
37
+ // Compiler forwards `cornerRadius` → `radius` (compile.ts). A frame can be a
38
+ // rounded container (Figma pills, the rounded picto square) — apply it as
39
+ // `border-radius` so the frame isn't rendered square.
40
+ const radius = numberOr(resolved.radius, 0);
27
41
  // 1.0 single-fill prop — used as fallback when 1.1 `backgrounds[]`
28
42
  // is empty. RC#11 : the value is untrusted (static prop OR live LSDP
29
43
  // delta) and lands in inline CSS — strict-parse, never passthrough.
@@ -32,7 +46,11 @@ export function Frame({ resolved, nodeId, transitionFor, animateInitial, childre
32
46
  if (rawBackground !== undefined && legacyBackground === null) {
33
47
  warnRejectedColor("frame.background", nodeId);
34
48
  }
35
- const backgrounds = parseFills(resolved.backgrounds, "frame.backgrounds", nodeId);
49
+ // LSML 1.2 §3.2 — image-fill `src` is host/scheme-gated (Bastion T1/T2)
50
+ // BEFORE any URL reaches `background-image`. A rejected image-fill is
51
+ // dropped (no passthrough) with an R9-clean diagnostic.
52
+ const allowedHosts = useAllowedHosts();
53
+ const backgrounds = gateImageFills(parseFills(resolved.backgrounds, "frame.backgrounds", nodeId), allowedHosts, "frame.backgrounds", nodeId);
36
54
  const clipsContent = resolveClipsContent(resolved.clipsContent, nodeId);
37
55
  // Pick the most expressive declared transition among the animated
38
56
  // bindings (transform / opacity). If none, no animation.
@@ -43,12 +61,18 @@ export function Frame({ resolved, nodeId, transitionFor, animateInitial, childre
43
61
  top: 0,
44
62
  width,
45
63
  height,
46
- willChange: "transform, opacity",
64
+ // NB: NO permanent `will-change`. `will-change: opacity` makes the frame an
65
+ // isolated group (the browser pre-promotes it as if opacity < 1), which
66
+ // CONTAINS any descendant `mix-blend-mode` to the frame's own backdrop — so
67
+ // a screen/hard-light layer (Sunshine, Ruby20) silently stops compositing
68
+ // with the scene below. The hint also belongs only on actively-animating
69
+ // nodes (bind-animate adds it there) ; a static board doesn't need it.
47
70
  // LSML 1.1 §4.3 `clipsContent` (default `true`) — children outside
48
71
  // the frame's `size` are clipped. Static layout property : it never
49
72
  // animates, so it stays off the 0-layout-event hot path (ADR 001
50
73
  // §3.2.5). `false` => omit the declaration (CSS initial = visible).
51
74
  ...(clipsContent ? { overflow: "hidden" } : {}),
75
+ ...(radius > 0 ? { borderRadius: radius } : {}),
52
76
  };
53
77
  if (backgrounds.length > 0) {
54
78
  Object.assign(style, backgroundsToCss(backgrounds, nodeId));
@@ -56,7 +80,20 @@ export function Frame({ resolved, nodeId, transitionFor, animateInitial, childre
56
80
  else if (legacyBackground !== undefined && legacyBackground !== null) {
57
81
  style.background = legacyBackground;
58
82
  }
59
- const play = mountPlay({ opacity, x, y, scale, rotate }, animateInitial, nodeId);
83
+ // Figma DROP_SHADOW / INNER_SHADOW. INNER CSS `box-shadow: inset` (the
84
+ // square's orange/red rim, on the rotated rounded frame, rotates with it in
85
+ // local space — matches Figma). A no-spread DROP → CSS `filter: drop-shadow`,
86
+ // which casts the shadow from the element's RENDERED CONTENT silhouette, not
87
+ // its own rectangular box : the 5 drop shadows live on the UN-rotated wrapper
88
+ // GROUP, so a plain `box-shadow` would project a sharp axis-aligned 464² rect
89
+ // instead of the rotated (8.63°) rounded (r=111) square held inside. The
90
+ // colour is strict-parsed (RC#11) ; geometry is numeric.
91
+ const { filter: shadowFilter, boxShadow } = buildShadows(resolved.shadow, nodeId);
92
+ if (boxShadow !== undefined)
93
+ style.boxShadow = boxShadow;
94
+ if (shadowFilter !== undefined)
95
+ style.filter = shadowFilter;
96
+ const play = mountPlay({ opacity, x, y, scale, rotate, ...(flipY ? { scaleY: -1 } : {}) }, animateInitial, nodeId);
60
97
  return (_jsx(motion.div, { style: style, initial: play.initial, animate: play.animate, transition: toFramer(tx), children: children }));
61
98
  }
62
99
  /**
@@ -81,6 +118,55 @@ export function resolveClipsContent(v, nodeId) {
81
118
  function numberOr(v, fallback) {
82
119
  return typeof v === "number" && Number.isFinite(v) ? v : fallback;
83
120
  }
121
+ /** Build validated shadow CSS from the node's `shadow[]` (each entry:
122
+ * `{ inset?, color, x, y, blur, spread }`). Every colour goes through the
123
+ * strict `parseCssColor` gate (RC#11 : the value is wire-drivable) — a
124
+ * rejected colour drops that layer with a diagnostic, never reaches CSS.
125
+ * Geometry values are coerced to finite numbers.
126
+ *
127
+ * Splits by kind :
128
+ * - INNER (inset) OR any shadow with a non-zero spread → `box-shadow`
129
+ * (inset rim / spread halo — both follow the element's own border box,
130
+ * which for the rotated rounded square IS the right silhouette).
131
+ * - no-spread DROP → `filter: drop-shadow`, cast from the element's rendered
132
+ * CONTENT (so a wrapper group's drop shadow tracks its rotated/rounded
133
+ * child instead of the wrapper's rectangular box). drop-shadow's blur maps
134
+ * to a Gaussian stdDeviation ; box-shadow uses 2×σ, so halve to match.
135
+ * Returns `{}` when nothing usable survives. */
136
+ function buildShadows(value, nodeId) {
137
+ if (!Array.isArray(value) || value.length === 0)
138
+ return {};
139
+ const dropParts = [];
140
+ const boxParts = [];
141
+ for (const s of value) {
142
+ if (typeof s !== "object" || s === null)
143
+ continue;
144
+ const spec = s;
145
+ const color = typeof spec.color === "string" ? parseCssColor(spec.color) : null;
146
+ if (color === null) {
147
+ warnRejectedColor("frame.shadow.color", nodeId);
148
+ continue;
149
+ }
150
+ const x = numberOr(spec.x, 0);
151
+ const y = numberOr(spec.y, 0);
152
+ const blur = numberOr(spec.blur, 0);
153
+ const spread = numberOr(spec.spread, 0);
154
+ const inset = spec.inset === true;
155
+ if (!inset && spread === 0) {
156
+ dropParts.push(`drop-shadow(${x}px ${y}px ${blur / 2}px ${color})`);
157
+ }
158
+ else {
159
+ const insetKw = inset ? "inset " : "";
160
+ boxParts.push(`${insetKw}${x}px ${y}px ${blur}px ${spread}px ${color}`);
161
+ }
162
+ }
163
+ const out = {};
164
+ if (dropParts.length > 0)
165
+ out.filter = dropParts.join(" ");
166
+ if (boxParts.length > 0)
167
+ out.boxShadow = boxParts.join(", ");
168
+ return out;
169
+ }
84
170
  function sizeProp(v) {
85
171
  if (typeof v === "number" && Number.isFinite(v))
86
172
  return v;
@@ -1 +1 @@
1
- {"version":3,"file":"frame.js","sourceRoot":"","sources":["../../../src/render/primitives/frame.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAGvC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,KAAK,CAAC,EACpB,QAAQ,EACR,MAAM,EACN,aAAa,EACb,cAAc,EACd,QAAQ,GACO;IACf,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClC,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAE5C,mEAAmE;IACnE,qEAAqE;IACrE,oEAAoE;IACpE,MAAM,aAAa,GAAG,QAAQ,CAAC,UAAU,CAAC;IAC1C,MAAM,gBAAgB,GAAG,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;IAChG,IAAI,aAAa,KAAK,SAAS,IAAI,gBAAgB,KAAK,IAAI,EAAE,CAAC;QAC7D,iBAAiB,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IACD,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAClF,MAAM,YAAY,GAAG,mBAAmB,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAExE,kEAAkE;IAClE,yDAAyD;IACzD,MAAM,EAAE,GAAG,iBAAiB,CAC1B,aAAa,EACb,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,CAAC,EACxC,cAAc,CACf,CAAC;IAEF,MAAM,KAAK,GAAkB;QAC3B,QAAQ,EAAE,UAAU;QACpB,IAAI,EAAE,CAAC;QACP,GAAG,EAAE,CAAC;QACN,KAAK;QACL,MAAM;QACN,UAAU,EAAE,oBAAoB;QAChC,mEAAmE;QACnE,oEAAoE;QACpE,iEAAiE;QACjE,oEAAoE;QACpE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC;IACF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,gBAAgB,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9D,CAAC;SAAM,IAAI,gBAAgB,KAAK,SAAS,IAAI,gBAAgB,KAAK,IAAI,EAAE,CAAC;QACvE,KAAK,CAAC,UAAU,GAAG,gBAAgB,CAAC;IACtC,CAAC;IAED,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IAEjF,OAAO,CACL,KAAC,MAAM,CAAC,GAAG,IACT,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC,YAEvB,QAAQ,GACE,CACd,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAU,EAAE,MAAe;IAC7D,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC;IACrC,cAAc,CAAC,MAAM,EAAE,oBAAoB,EAAE,gCAAgC,CAAC,CAAC;IAC/E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU,EAAE,QAAgB;IAC5C,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACpE,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU;IAC1B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1D,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACpD,OAAO,SAAS,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"frame.js","sourceRoot":"","sources":["../../../src/render/primitives/frame.tsx"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  import type { PrimitiveProps } from "./index";
2
2
  /** CSS Grid container with declared rows / cols. */
3
- export declare function Grid({ resolved, children }: PrimitiveProps): import("react/jsx-runtime").JSX.Element;
3
+ export declare function Grid({ resolved, children, establishesContainingBlock }: PrimitiveProps): import("react/jsx-runtime").JSX.Element;
4
4
  //# sourceMappingURL=grid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/grid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,oDAAoD;AACpD,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,cAAc,2CAgB1D"}
1
+ {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/grid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,oDAAoD;AACpD,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,0BAA0B,EAAE,EAAE,cAAc,2CAmBtF"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /** CSS Grid container with declared rows / cols. */
3
- export function Grid({ resolved, children }) {
3
+ export function Grid({ resolved, children, establishesContainingBlock }) {
4
4
  const cols = resolved.cols ?? "1fr";
5
5
  const rows = resolved.rows ?? "auto";
6
6
  const gap = resolved.gap ?? 0;
@@ -9,6 +9,9 @@ export function Grid({ resolved, children }) {
9
9
  gridTemplateColumns: cols,
10
10
  gridTemplateRows: rows,
11
11
  gap,
12
+ // ADR 002 §3.1 (D1) — establish a containing block for absolutely
13
+ // placed children ; untouched for pure auto-layout grids (RC#2).
14
+ ...(establishesContainingBlock ? { position: "relative" } : {}),
12
15
  }, children: children }));
13
16
  }
14
17
  //# sourceMappingURL=grid.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"grid.js","sourceRoot":"","sources":["../../../src/render/primitives/grid.tsx"],"names":[],"mappings":";AAEA,oDAAoD;AACpD,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAkB;IACzD,MAAM,IAAI,GAAI,QAAQ,CAAC,IAAe,IAAI,KAAK,CAAC;IAChD,MAAM,IAAI,GAAI,QAAQ,CAAC,IAAe,IAAI,MAAM,CAAC;IACjD,MAAM,GAAG,GAAI,QAAQ,CAAC,GAAmC,IAAI,CAAC,CAAC;IAC/D,OAAO,CACL,cACE,KAAK,EAAE;YACL,OAAO,EAAE,MAAM;YACf,mBAAmB,EAAE,IAAI;YACzB,gBAAgB,EAAE,IAAI;YACtB,GAAG;SACJ,YAEA,QAAQ,GACL,CACP,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"grid.js","sourceRoot":"","sources":["../../../src/render/primitives/grid.tsx"],"names":[],"mappings":";AAEA,oDAAoD;AACpD,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,0BAA0B,EAAkB;IACrF,MAAM,IAAI,GAAI,QAAQ,CAAC,IAAe,IAAI,KAAK,CAAC;IAChD,MAAM,IAAI,GAAI,QAAQ,CAAC,IAAe,IAAI,MAAM,CAAC;IACjD,MAAM,GAAG,GAAI,QAAQ,CAAC,GAAmC,IAAI,CAAC,CAAC;IAC/D,OAAO,CACL,cACE,KAAK,EAAE;YACL,OAAO,EAAE,MAAM;YACf,mBAAmB,EAAE,IAAI;YACzB,gBAAgB,EAAE,IAAI;YACtB,GAAG;YACH,kEAAkE;YAClE,iEAAiE;YACjE,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChE,YAEA,QAAQ,GACL,CACP,CAAC;AACJ,CAAC"}
@@ -2,6 +2,13 @@ import type { PrimitiveProps } from "./index";
2
2
  /** Image leaf. `src`, `fit` (cover/contain/fill), `position`,
3
3
  * `opacity`. Opacity is animated when a transition is declared. When an
4
4
  * `animate.from` is lowered onto the node, it mounts at that state and
5
- * plays to its target on mount (mount-play). */
5
+ * plays to its target on mount (mount-play).
6
+ *
7
+ * Security (Bastion T1/T2, ADR 002 #F) : `src` is untrusted (static prop
8
+ * OR live LSDP delta) and was placed into the DOM with NO host/scheme
9
+ * check until #F (the latent 1.1 hole — `assets.allowedHosts` was declared
10
+ * but never enforced). It now passes `gateSrc` BEFORE reaching the `<img>`
11
+ * — a rejected host/scheme omits the image entirely (no passthrough), with
12
+ * an R9-clean diagnostic. This is the runtime arm of the double-gate. */
6
13
  export declare function Image({ resolved, nodeId, transitionFor, animateInitial }: PrimitiveProps): import("react/jsx-runtime").JSX.Element | null;
7
14
  //# sourceMappingURL=image.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/image.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C;;;iDAGiD;AACjD,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,cAAc,kDAmCxF"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/image.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAI9C;;;;;;;;;;0EAU0E;AAC1E,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,EAAE,cAAc,kDAyCxF"}
@@ -1,12 +1,21 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { motion } from "framer-motion";
3
3
  import { toFramer, mountPlay, resolveTransition } from "../../animate/transitions";
4
+ import { gateSrc, useAllowedHosts } from "../allowed-hosts";
4
5
  /** Image leaf. `src`, `fit` (cover/contain/fill), `position`,
5
6
  * `opacity`. Opacity is animated when a transition is declared. When an
6
7
  * `animate.from` is lowered onto the node, it mounts at that state and
7
- * plays to its target on mount (mount-play). */
8
+ * plays to its target on mount (mount-play).
9
+ *
10
+ * Security (Bastion T1/T2, ADR 002 #F) : `src` is untrusted (static prop
11
+ * OR live LSDP delta) and was placed into the DOM with NO host/scheme
12
+ * check until #F (the latent 1.1 hole — `assets.allowedHosts` was declared
13
+ * but never enforced). It now passes `gateSrc` BEFORE reaching the `<img>`
14
+ * — a rejected host/scheme omits the image entirely (no passthrough), with
15
+ * an R9-clean diagnostic. This is the runtime arm of the double-gate. */
8
16
  export function Image({ resolved, nodeId, transitionFor, animateInitial }) {
9
- const src = resolved.src;
17
+ const allowedHosts = useAllowedHosts();
18
+ const src = gateSrc(resolved.src, allowedHosts, "image.src", nodeId);
10
19
  if (!src)
11
20
  return null;
12
21
  // LSML §4.5 `alt` is required and was silently unrendered until
@@ -27,7 +36,12 @@ export function Image({ resolved, nodeId, transitionFor, animateInitial }) {
27
36
  objectPosition: position,
28
37
  width,
29
38
  height,
30
- willChange: "opacity, transform",
39
+ // NB: NO `will-change` here. Promoting the <img> to its own GPU layer
40
+ // hoists it out of the wrapper's paint buffer, so a `mix-blend-mode`
41
+ // on the wrapper (Sunshine screen, Ruby20 / caramel hard-light) blends
42
+ // an EMPTY box with the backdrop → the blend silently no-ops and the
43
+ // image's contribution (the diagonal light streaks, the warm Ruby) is
44
+ // lost. Static images don't need the compositor hint anyway.
31
45
  }, initial: play.initial, animate: play.animate, transition: toFramer(tx), draggable: false }));
32
46
  }
33
47
  function numberOr(v, fallback) {
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","sourceRoot":"","sources":["../../../src/render/primitives/image.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnF;;;iDAGiD;AACjD,MAAM,UAAU,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAkB;IACvF,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAyB,CAAC;IAC/C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,gEAAgE;IAChE,sEAAsE;IACtE,MAAM,GAAG,GAAG,OAAO,QAAQ,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,MAAM,GAAG,GAAI,QAAQ,CAAC,GAA0B,IAAI,SAAS,CAAC;IAC9D,MAAM,QAAQ,GAAI,QAAQ,CAAC,QAA+B,IAAI,QAAQ,CAAC;IACvE,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC9C,mFAAmF;IACnF,0EAA0E;IAC1E,sEAAsE;IACtE,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE9C,MAAM,EAAE,GAAG,iBAAiB,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,cAAc,CAAC,CAAC;IAChF,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IAE5D,OAAO,CACL,KAAC,MAAM,CAAC,GAAG,IACT,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,GAAG,EACR,KAAK,EAAE;YACL,SAAS,EAAE,GAAuC;YAClD,cAAc,EAAE,QAAQ;YACxB,KAAK;YACL,MAAM;YACN,UAAU,EAAE,oBAAoB;SACjC,EACD,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC,EACxB,SAAS,EAAE,KAAK,GAChB,CACH,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU,EAAE,QAAgB;IAC5C,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACpE,CAAC;AAED;mDACmD;AACnD,SAAS,KAAK,CAAC,CAAU,EAAE,QAAgB;IACzC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC,IAAI,CAAC;IACjE,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACpD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../../src/render/primitives/image.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;;;;;;;;;0EAU0E;AAC1E,MAAM,UAAU,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAkB;IACvF,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IACrE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,gEAAgE;IAChE,sEAAsE;IACtE,MAAM,GAAG,GAAG,OAAO,QAAQ,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,MAAM,GAAG,GAAI,QAAQ,CAAC,GAA0B,IAAI,SAAS,CAAC;IAC9D,MAAM,QAAQ,GAAI,QAAQ,CAAC,QAA+B,IAAI,QAAQ,CAAC;IACvE,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC9C,mFAAmF;IACnF,0EAA0E;IAC1E,sEAAsE;IACtE,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE9C,MAAM,EAAE,GAAG,iBAAiB,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,cAAc,CAAC,CAAC;IAChF,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IAE5D,OAAO,CACL,KAAC,MAAM,CAAC,GAAG,IACT,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,GAAG,EACR,KAAK,EAAE;YACL,SAAS,EAAE,GAAuC;YAClD,cAAc,EAAE,QAAQ;YACxB,KAAK;YACL,MAAM;YACN,sEAAsE;YACtE,qEAAqE;YACrE,uEAAuE;YACvE,qEAAqE;YACrE,sEAAsE;YACtE,6DAA6D;SAC9D,EACD,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,OAAO,EAAE,IAAI,CAAC,OAAO,EACrB,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC,EACxB,SAAS,EAAE,KAAK,GAChB,CACH,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU,EAAE,QAAgB;IAC5C,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACpE,CAAC;AAED;mDACmD;AACnD,SAAS,KAAK,CAAC,CAAU,EAAE,QAAgB;IACzC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC,IAAI,CAAC;IACjE,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACpD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -13,6 +13,13 @@ export interface PrimitiveProps {
13
13
  * element mounts in this state and animates to its rendered target on
14
14
  * mount (mount-play). `undefined` → no `initial` (no mount-play). */
15
15
  animateInitial?: Record<string, number | string>;
16
+ /** ADR 002 §3.1 (D1) — set by the Tree when this node has at least one
17
+ * absolutely positioned child. A layout container (`stack`/`grid`)
18
+ * flips to `position: relative` so its children's `left/top` resolve
19
+ * against it. `frame` is already `position: absolute` (a containing
20
+ * block) and ignores it ; leaf primitives have no children and ignore
21
+ * it too. `false`/absent → no change (pure auto-layout, RC#2). */
22
+ establishesContainingBlock?: boolean;
16
23
  children?: ReactNode;
17
24
  }
18
25
  export declare const PRIMITIVES: Partial<Record<RenderKind, ComponentType<PrimitiveProps>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAa5D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC;oEACgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IACvD;;;;0EAIsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC,CASjF,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAa5D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC;oEACgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IACvD;;;;0EAIsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IACjD;;;;;uEAKmE;IACnE,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC,CASjF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/render/primitives/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,uEAAuE;AACvE,0DAA0D;AAK1D,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAoBtC,MAAM,CAAC,MAAM,UAAU,GAA+D;IACpF,KAAK,EAAE,KAAK;IACZ,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,KAAK;IACZ,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,QAAQ,EAAE,QAAQ;CACnB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/render/primitives/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,uEAAuE;AACvE,0DAA0D;AAK1D,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA2BtC,MAAM,CAAC,MAAM,UAAU,GAA+D;IACpF,KAAK,EAAE,KAAK;IACZ,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,KAAK;IACZ,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,QAAQ,EAAE,QAAQ;CACnB,CAAC"}
@@ -1,6 +1,15 @@
1
1
  import type { PrimitiveProps } from "./index";
2
2
  /** Embedded video. `src`, `loop`, `mute`, `autoplay`. Audio is muted
3
3
  * by default — broadcast audio is Pulsar-side, not from the browser
4
- * source. */
5
- export declare function Media({ resolved }: PrimitiveProps): import("react/jsx-runtime").JSX.Element | null;
4
+ * source.
5
+ *
6
+ * Security (Bastion, ADR 003) : `src` is the media primitive's sole
7
+ * network sink (no `poster` / `<source>` / `<track>` are rendered). Like
8
+ * every other asset leaf (image / image-fill / mask) it MUST pass
9
+ * `gateSrc` BEFORE reaching the `<video>` — otherwise a `kind:"media"`
10
+ * node would make the headless Chromium of `zabrender` emit an
11
+ * off-allowlist request (an SSRF surface). A rejected host/scheme omits
12
+ * the source entirely (no passthrough), with an R9-clean diagnostic
13
+ * ({ nodeId, field, reason } — never the URL). */
14
+ export declare function Media({ resolved, nodeId }: PrimitiveProps): import("react/jsx-runtime").JSX.Element | null;
6
15
  //# sourceMappingURL=media.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/media.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;cAEc;AACd,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,cAAc,kDAsBjD"}
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/render/primitives/media.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C;;;;;;;;;;;mDAWmD;AACnD,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,cAAc,kDAuBzD"}
@@ -1,9 +1,20 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { gateSrc, useAllowedHosts } from "../allowed-hosts";
2
3
  /** Embedded video. `src`, `loop`, `mute`, `autoplay`. Audio is muted
3
4
  * by default — broadcast audio is Pulsar-side, not from the browser
4
- * source. */
5
- export function Media({ resolved }) {
6
- const src = resolved.src;
5
+ * source.
6
+ *
7
+ * Security (Bastion, ADR 003) : `src` is the media primitive's sole
8
+ * network sink (no `poster` / `<source>` / `<track>` are rendered). Like
9
+ * every other asset leaf (image / image-fill / mask) it MUST pass
10
+ * `gateSrc` BEFORE reaching the `<video>` — otherwise a `kind:"media"`
11
+ * node would make the headless Chromium of `zabrender` emit an
12
+ * off-allowlist request (an SSRF surface). A rejected host/scheme omits
13
+ * the source entirely (no passthrough), with an R9-clean diagnostic
14
+ * ({ nodeId, field, reason } — never the URL). */
15
+ export function Media({ resolved, nodeId }) {
16
+ const allowedHosts = useAllowedHosts();
17
+ const src = gateSrc(resolved.src, allowedHosts, "media.src", nodeId);
7
18
  if (!src)
8
19
  return null;
9
20
  const loop = resolved.loop ?? true;