camox 0.56.0 → 0.58.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 (38) hide show
  1. package/dist/core/createApp.d.ts +2 -2
  2. package/dist/core/createBlock.js +6 -2
  3. package/dist/core/createLayout.d.ts +1 -1
  4. package/dist/core/editing/createEditableBlock.d.ts +1 -1
  5. package/dist/core/lib/artifact.d.ts +14 -4
  6. package/dist/core/lib/artifact.js +57 -16
  7. package/dist/core/lib/artifactRuntime.js +4 -2
  8. package/dist/features/canvas/CanvasPageHeader.js +1 -1
  9. package/dist/features/canvas/canvasFrameDocument.js +13 -6
  10. package/dist/features/preview/components/Frame.js +1 -18
  11. package/dist/features/preview/components/SidebarStack.js +193 -106
  12. package/dist/features/preview/components/previewStyles.js +25 -1
  13. package/dist/features/renderer/RendererApp.js +55 -37
  14. package/dist/features/runtime/artifactDocuments.js +58 -0
  15. package/dist/features/runtime/artifactSources.js +36 -2
  16. package/dist/features/runtime/runtime.js +30 -12
  17. package/dist/features/runtime/runtimeRoutes.d.ts +6 -5
  18. package/dist/features/runtime/runtimeRoutes.js +12 -5
  19. package/dist/features/studio/components/AgentSidebar.js +4 -4
  20. package/dist/features/studio/components/ChatWidget.js +62 -80
  21. package/dist/features/studio/components/DraftsControl.js +60 -134
  22. package/dist/features/studio/components/Patrol.js +676 -0
  23. package/dist/features/studio/components/ReviewDraftSheet.js +52 -4
  24. package/dist/features/studio/components/Squareling.js +169 -84
  25. package/dist/features/studio/components/ThreadMessages.js +155 -58
  26. package/dist/features/studio/localeOptions.js +7 -3
  27. package/dist/features/studio/reportSentence.js +75 -0
  28. package/dist/features/studio/routes.js +2 -1
  29. package/dist/features/studio/showInCanvas.js +9 -2
  30. package/dist/features/studio/workingSentence.js +55 -0
  31. package/dist/lib/studio-reads.d.ts +1 -0
  32. package/dist/lib/studio-reads.js +5 -0
  33. package/dist/lib/use-thread.js +156 -190
  34. package/dist/package.js +1 -1
  35. package/dist/studio.css +1 -1
  36. package/package.json +4 -4
  37. package/dist/features/studio/components/RenameDraftDialog.js +0 -206
  38. package/dist/features/studio/components/WorkersWidget.js +0 -52
@@ -64,16 +64,8 @@ const LEAVE_DURATION = 180;
64
64
  const SWAP_DURATION = 150;
65
65
  /** How long after each other cards laid at once land, front-most last. */
66
66
  const STAGGER = 25;
67
- const HEADER_TRANSITION = [...[
68
- "transform",
69
- "margin",
70
- "height",
71
- "padding"
72
- ].map((p) => `${p} ${DURATION}ms ${EASE}`), ...[
73
- "background-color",
74
- "box-shadow",
75
- "color"
76
- ].map((p) => `${p} ${DURATION}ms ease-out`)].join(", ");
67
+ /** How far any card shades at most, `stepSeries`' limit: a header's shade is a part of it. */
68
+ const MAX_SHADE = FIRST_STEP_SHADE / .15000000000000002;
77
69
  /** How far below the front a card is laid from, or pulled away to, in px. */
78
70
  const PULL = 24;
79
71
  const PLACED = {
@@ -312,6 +304,41 @@ function useStackCard(ref, depth, isView) {
312
304
  };
313
305
  }, []);
314
306
  }
307
+ /**
308
+ * Eases a header's card and content from where they were laid out before `shape` changed to
309
+ * where they are now, as its layout snaps: its card scales from its old size, and its content
310
+ * slides from its old height.
311
+ */
312
+ function useHeaderReshape(surfaceRef, contentRef, shape) {
313
+ const last = React.useRef(null);
314
+ const moving = React.useRef([]);
315
+ React.useLayoutEffect(() => {
316
+ const surface = surfaceRef.current;
317
+ const content = contentRef.current;
318
+ if (!surface || !content) return;
319
+ const box = {
320
+ width: surface.offsetWidth,
321
+ height: surface.offsetHeight,
322
+ center: content.offsetTop + content.offsetHeight / 2
323
+ };
324
+ const from = last.current;
325
+ last.current = box;
326
+ if (!from || !box.width || !box.height || !prefersMotion()) return;
327
+ let { width, height, center } = from;
328
+ if (moving.current.some((animation) => animation.playState === "running")) {
329
+ const shown = new DOMMatrixReadOnly(getComputedStyle(surface).transform);
330
+ width *= shown.a;
331
+ height *= shown.d;
332
+ center += new DOMMatrixReadOnly(getComputedStyle(content).transform).f;
333
+ }
334
+ for (const animation of moving.current) animation.cancel();
335
+ const timing = {
336
+ duration: DURATION,
337
+ easing: EASE
338
+ };
339
+ moving.current = [surface.animate([{ transform: `scale(${width / box.width}, ${height / box.height})` }, { transform: "none" }], timing), content.animate([{ transform: `translateY(${center - box.center}px)` }, { transform: "none" }], timing)];
340
+ }, [shape]);
341
+ }
315
342
  /** How many of the selection's views are laid over the page's, in this stack. */
316
343
  function useSelectionViewCount() {
317
344
  return React.useContext(SelectionViewsContext)?.count ?? 0;
@@ -372,7 +399,7 @@ function SidebarStackView(t0) {
372
399
  return t3;
373
400
  }
374
401
  function SidebarStackHeader(t0) {
375
- const $ = c(55);
402
+ const $ = c(69);
376
403
  const { depth, distance: t1, first: t2, onSelect, hoverTarget, tooltip, popover, children } = t0;
377
404
  const distance = t1 === void 0 ? 0 : t1;
378
405
  const first = t2 === void 0 ? false : t2;
@@ -450,82 +477,129 @@ function SidebarStackHeader(t0) {
450
477
  $[16] = t8;
451
478
  } else t8 = $[16];
452
479
  const button = t8;
453
- const t9 = !first && "-mt-1";
454
- let t10;
455
- if ($[17] !== t9) {
456
- t10 = cn("relative shrink-0 origin-top", t9);
457
- $[17] = t9;
458
- $[18] = t10;
459
- } else t10 = $[18];
460
- const t11 = !flat && !first && "rounded-t-sm";
480
+ const surfaceRef = React.useRef(null);
481
+ const contentRef = React.useRef(null);
482
+ let t9;
483
+ if ($[17] !== covered || $[18] !== distance) {
484
+ t9 = covered ? stackInset(distance) : 0;
485
+ $[17] = covered;
486
+ $[18] = distance;
487
+ $[19] = t9;
488
+ } else t9 = $[19];
489
+ const inset = t9;
490
+ useHeaderReshape(surfaceRef, contentRef, `${covered}:${inset}`);
491
+ const t10 = !first && "-mt-1";
492
+ let t11;
493
+ if ($[20] !== t10) {
494
+ t11 = cn("relative shrink-0 origin-top", t10);
495
+ $[20] = t10;
496
+ $[21] = t11;
497
+ } else t11 = $[21];
461
498
  const t12 = covered ? "h-[38px] pb-1" : "h-11 px-3";
462
- const t13 = first && !flat && "rounded-t-lg inset-shadow-raised-top";
463
- const t14 = covered && !first && `${CARD_TOP_EDGE} shadow-[0_-2px_5px_-3px_rgb(0_0_0/0.08)] dark:shadow-[0_-2px_5px_-3px_rgb(0_0_0/0.4)]`;
464
- const t15 = !covered && !first && `${CARD_TOP_EDGE} shadow-[0_-3px_8px_-4px_rgb(0_0_0/0.08)] dark:shadow-[0_-3px_8px_-4px_rgb(0_0_0/0.4)]`;
465
- const t16 = liftable && "hover:[--fade:1] hover:[--lift:var(--hover-lift)] hover:[--shade:var(--hover-shade)] hover:[--tilt:var(--hover-tilt)]";
466
- const t17 = liftable && "has-focus-visible:[--fade:1] has-focus-visible:[--lift:var(--hover-lift)] has-focus-visible:[--shade:var(--hover-shade)] has-focus-visible:[--tilt:var(--hover-tilt)]";
467
- const t18 = liftable && "has-data-popup-open:[--fade:1] has-data-popup-open:[--lift:var(--hover-lift)] has-data-popup-open:[--shade:var(--hover-shade)] has-data-popup-open:[--tilt:var(--hover-tilt)]";
468
- let t19;
469
- if ($[19] !== t11 || $[20] !== t12 || $[21] !== t13 || $[22] !== t14 || $[23] !== t15 || $[24] !== t16 || $[25] !== t17 || $[26] !== t18) {
470
- t19 = cn("relative flex min-w-0 items-center justify-center text-center text-sm", t11, "[--ancestor-foreground:color-mix(in_oklab,var(--color-muted-foreground),var(--color-foreground)_40%)]", "[--fade:var(--rest-fade)] [--lift:0px] [--shade:var(--rest-shade)] [--tilt:var(--rest-tilt)]", "[--shade-color:black] [--shade-strength:1] dark:[--shade-color:white] dark:[--shade-strength:var(--dark-shade-strength)]", "origin-bottom [backface-visibility:hidden] motion-reduce:transition-none!", t12, t13, t14, t15, t16, t17, t18);
471
- $[19] = t11;
472
- $[20] = t12;
473
- $[21] = t13;
474
- $[22] = t14;
475
- $[23] = t15;
476
- $[24] = t16;
477
- $[25] = t17;
478
- $[26] = t18;
499
+ const t13 = liftable && "hover:[--fade:1] hover:[--lift:var(--hover-lift)] hover:[--shade:var(--hover-shade)] hover:[--tilt:var(--hover-tilt)]";
500
+ const t14 = liftable && "has-focus-visible:[--fade:1] has-focus-visible:[--lift:var(--hover-lift)] has-focus-visible:[--shade:var(--hover-shade)] has-focus-visible:[--tilt:var(--hover-tilt)]";
501
+ const t15 = liftable && "has-data-popup-open:[--fade:1] has-data-popup-open:[--lift:var(--hover-lift)] has-data-popup-open:[--shade:var(--hover-shade)] has-data-popup-open:[--tilt:var(--hover-tilt)]";
502
+ let t16;
503
+ if ($[22] !== t12 || $[23] !== t13 || $[24] !== t14 || $[25] !== t15) {
504
+ t16 = cn("relative flex min-w-0 items-center justify-center text-center text-sm", "[--ancestor-foreground:color-mix(in_oklab,var(--color-muted-foreground),var(--color-foreground)_40%)]", "[--fade:var(--rest-fade)] [--lift:0px] [--shade:var(--rest-shade)] [--tilt:var(--rest-tilt)]", "[--shade-color:black] [--shade-strength:1] dark:[--shade-color:white] dark:[--shade-strength:var(--dark-shade-strength)]", "origin-bottom [backface-visibility:hidden] motion-reduce:transition-none!", t12, t13, t14, t15);
505
+ $[22] = t12;
506
+ $[23] = t13;
507
+ $[24] = t14;
508
+ $[25] = t15;
509
+ $[26] = t16;
510
+ } else t16 = $[26];
511
+ const t17 = `${tilt}deg`;
512
+ const t18 = `${tilt / 2}deg`;
513
+ const t19 = Math.max(0, distance - 1);
514
+ let t20;
515
+ if ($[27] !== t19) {
516
+ t20 = stepSeries(FIRST_STEP_SHADE, t19);
479
517
  $[27] = t19;
480
- } else t19 = $[27];
481
- const t20 = `${tilt}deg`;
482
- const t21 = `${tilt / 2}deg`;
483
- const t22 = `${shade}%`;
484
- const t23 = Math.max(0, distance - 1);
485
- let t24;
486
- if ($[28] !== t23) {
487
- t24 = stepSeries(FIRST_STEP_SHADE, t23);
488
- $[28] = t23;
489
- $[29] = t24;
490
- } else t24 = $[29];
491
- const t25 = `${t24}%`;
492
- const t26 = `${first ? 0 : -1.5}px`;
493
- const t27 = covered ? 1 - stepSeries(FIRST_STEP_FADE, distance - 1) : 1;
494
- let t28;
495
- if ($[30] !== covered || $[31] !== distance) {
496
- t28 = covered ? stackInset(distance) : void 0;
497
- $[30] = covered;
498
- $[31] = distance;
499
- $[32] = t28;
500
- } else t28 = $[32];
501
- let t29;
502
- if ($[33] !== t20 || $[34] !== t21 || $[35] !== t22 || $[36] !== t25 || $[37] !== t26 || $[38] !== t27 || $[39] !== t28) {
503
- t29 = {
504
- "--rest-tilt": t20,
505
- "--hover-tilt": t21,
506
- "--rest-shade": t22,
518
+ $[28] = t20;
519
+ } else t20 = $[28];
520
+ const t21 = `${first ? 0 : -1.5}px`;
521
+ const t22 = covered ? 1 - stepSeries(FIRST_STEP_FADE, distance - 1) : 1;
522
+ let t23;
523
+ if ($[29] !== shade || $[30] !== t17 || $[31] !== t18 || $[32] !== t20 || $[33] !== t21 || $[34] !== t22) {
524
+ t23 = {
525
+ "--rest-tilt": t17,
526
+ "--hover-tilt": t18,
527
+ "--rest-shade": shade,
507
528
  "--dark-shade-strength": DARK_SHADE_STRENGTH,
508
- "--hover-shade": t25,
509
- "--hover-lift": t26,
510
- "--rest-fade": t27,
511
- marginInline: t28,
529
+ "--hover-shade": t20,
530
+ "--hover-lift": t21,
531
+ "--rest-fade": t22,
512
532
  transform: `perspective(${PERSPECTIVE}px) translateY(var(--lift)) rotateX(var(--tilt))`,
513
- backgroundColor: "color-mix(in oklab, var(--color-background), var(--shade-color) calc(var(--shade) * var(--shade-strength)))",
514
- transition: HEADER_TRANSITION
533
+ transition: `transform ${DURATION}ms ${EASE}`
515
534
  };
516
- $[33] = t20;
517
- $[34] = t21;
518
- $[35] = t22;
535
+ $[29] = shade;
536
+ $[30] = t17;
537
+ $[31] = t18;
538
+ $[32] = t20;
539
+ $[33] = t21;
540
+ $[34] = t22;
541
+ $[35] = t23;
542
+ } else t23 = $[35];
543
+ const t24 = t23;
544
+ const t25 = !flat && (first ? "rounded-t-lg" : "rounded-t-sm");
545
+ const t26 = covered && !first && "shadow-[0_-2px_5px_-3px_rgb(0_0_0/0.08)] dark:shadow-[0_-2px_5px_-3px_rgb(0_0_0/0.4)]";
546
+ const t27 = !covered && !first && "shadow-[0_-3px_8px_-4px_rgb(0_0_0/0.08)] dark:shadow-[0_-3px_8px_-4px_rgb(0_0_0/0.4)]";
547
+ let t28;
548
+ if ($[36] !== t25 || $[37] !== t26 || $[38] !== t27) {
549
+ t28 = cn("bg-background absolute inset-y-0 isolate origin-top", t25, t26, t27);
519
550
  $[36] = t25;
520
551
  $[37] = t26;
521
552
  $[38] = t27;
522
553
  $[39] = t28;
523
- $[40] = t29;
524
- } else t29 = $[40];
525
- const t30 = t29;
554
+ } else t28 = $[39];
555
+ let t29;
556
+ if ($[40] !== inset) {
557
+ t29 = { insetInline: inset };
558
+ $[40] = inset;
559
+ $[41] = t29;
560
+ } else t29 = $[41];
561
+ let t30;
562
+ if ($[42] === Symbol.for("react.memo_cache_sentinel")) {
563
+ t30 = /* @__PURE__ */ jsx("div", {
564
+ className: "absolute inset-0 rounded-[inherit] motion-reduce:transition-none!",
565
+ style: {
566
+ backgroundColor: `color-mix(in oklab, var(--color-background), var(--shade-color) calc(${MAX_SHADE}% * var(--shade-strength)))`,
567
+ opacity: `calc(var(--shade) / ${MAX_SHADE})`,
568
+ transition: `opacity ${DURATION}ms ease-out`
569
+ }
570
+ });
571
+ $[42] = t30;
572
+ } else t30 = $[42];
526
573
  let t31;
527
- if ($[41] !== button || $[42] !== children || $[43] !== covered || $[44] !== popover || $[45] !== popoverOpen || $[46] !== tooltip) {
528
- t31 = button && popover ? /* @__PURE__ */ jsxs(Popover, {
574
+ if ($[43] !== flat) {
575
+ t31 = !flat && /* @__PURE__ */ jsx("div", { className: cn("absolute inset-0 rounded-[inherit]", CARD_TOP_EDGE) });
576
+ $[43] = flat;
577
+ $[44] = t31;
578
+ } else t31 = $[44];
579
+ let t32;
580
+ if ($[45] !== t28 || $[46] !== t29 || $[47] !== t31) {
581
+ t32 = /* @__PURE__ */ jsxs("div", {
582
+ ref: surfaceRef,
583
+ "aria-hidden": true,
584
+ className: t28,
585
+ style: t29,
586
+ children: [t30, t31]
587
+ });
588
+ $[45] = t28;
589
+ $[46] = t29;
590
+ $[47] = t31;
591
+ $[48] = t32;
592
+ } else t32 = $[48];
593
+ const t33 = inset || void 0;
594
+ let t34;
595
+ if ($[49] !== t33) {
596
+ t34 = { marginInline: t33 };
597
+ $[49] = t33;
598
+ $[50] = t34;
599
+ } else t34 = $[50];
600
+ let t35;
601
+ if ($[51] !== button || $[52] !== children || $[53] !== covered || $[54] !== popover || $[55] !== popoverOpen || $[56] !== tooltip) {
602
+ t35 = button && popover ? /* @__PURE__ */ jsxs(Popover, {
529
603
  open: popoverOpen,
530
604
  onOpenChange: setPopoverOpen,
531
605
  children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: button }), /* @__PURE__ */ jsx(PopoverContent, {
@@ -542,38 +616,51 @@ function SidebarStackHeader(t0) {
542
616
  className: "truncate px-3 text-(--ancestor-foreground) opacity-(--fade)",
543
617
  children
544
618
  }) : children);
545
- $[41] = button;
546
- $[42] = children;
547
- $[43] = covered;
548
- $[44] = popover;
549
- $[45] = popoverOpen;
550
- $[46] = tooltip;
551
- $[47] = t31;
552
- } else t31 = $[47];
553
- let t32;
554
- if ($[48] !== t19 || $[49] !== t30 || $[50] !== t31) {
555
- t32 = /* @__PURE__ */ jsx("div", {
556
- className: t19,
557
- style: t30,
558
- children: t31
619
+ $[51] = button;
620
+ $[52] = children;
621
+ $[53] = covered;
622
+ $[54] = popover;
623
+ $[55] = popoverOpen;
624
+ $[56] = tooltip;
625
+ $[57] = t35;
626
+ } else t35 = $[57];
627
+ let t36;
628
+ if ($[58] !== t34 || $[59] !== t35) {
629
+ t36 = /* @__PURE__ */ jsx("div", {
630
+ ref: contentRef,
631
+ className: "relative flex min-w-0 flex-1 items-center justify-center self-stretch",
632
+ style: t34,
633
+ children: t35
634
+ });
635
+ $[58] = t34;
636
+ $[59] = t35;
637
+ $[60] = t36;
638
+ } else t36 = $[60];
639
+ let t37;
640
+ if ($[61] !== t16 || $[62] !== t24 || $[63] !== t32 || $[64] !== t36) {
641
+ t37 = /* @__PURE__ */ jsxs("div", {
642
+ className: t16,
643
+ style: t24,
644
+ children: [t32, t36]
559
645
  });
560
- $[48] = t19;
561
- $[49] = t30;
562
- $[50] = t31;
563
- $[51] = t32;
564
- } else t32 = $[51];
565
- let t33;
566
- if ($[52] !== t10 || $[53] !== t32) {
567
- t33 = /* @__PURE__ */ jsx("div", {
646
+ $[61] = t16;
647
+ $[62] = t24;
648
+ $[63] = t32;
649
+ $[64] = t36;
650
+ $[65] = t37;
651
+ } else t37 = $[65];
652
+ let t38;
653
+ if ($[66] !== t11 || $[67] !== t37) {
654
+ t38 = /* @__PURE__ */ jsx("div", {
568
655
  ref,
569
- className: t10,
570
- children: t32
656
+ className: t11,
657
+ children: t37
571
658
  });
572
- $[52] = t10;
573
- $[53] = t32;
574
- $[54] = t33;
575
- } else t33 = $[54];
576
- return t33;
659
+ $[66] = t11;
660
+ $[67] = t37;
661
+ $[68] = t38;
662
+ } else t38 = $[68];
663
+ return t38;
577
664
  }
578
665
 
579
666
  //#endregion
@@ -1,8 +1,32 @@
1
1
  //#region src/features/preview/components/previewStyles.ts
2
+ /**
3
+ * Whether a style or stylesheet is the site's. The studio marks its own, including those it adds
4
+ * to a frame's document, with `data-camox-studio`.
5
+ */
2
6
  function isSiteStyle(element) {
3
7
  return !element.hasAttribute("data-camox-studio");
4
8
  }
5
9
  const EMPTY_PREVIEW_DOCUMENT = "<!doctype html><html><head></head><body><div id=\"root\" data-camox-preview-root></div></body></html>";
10
+ /**
11
+ * The stylesheets of a document's head that are the site document's: not the studio's, nor those
12
+ * React hoists there for the blocks rendering in it (`data-precedence`).
13
+ */
14
+ const SITE_STYLESHEETS = "link[rel~=\"stylesheet\"], style";
15
+ const siteStylesheetsOf = (head) => Array.from(head.querySelectorAll(SITE_STYLESHEETS)).filter((element) => element.parentElement === head && isSiteStyle(element) && !element.hasAttribute("data-precedence"));
16
+ /**
17
+ * Gives a frame's document the site stylesheets of `html`, a site document: the ones it no
18
+ * longer has go, and its new ones are added, the rest of the frame left as it is.
19
+ */
20
+ function restyle(frame, html) {
21
+ const doc = frame.document;
22
+ const { DOMParser } = frame;
23
+ const wanted = siteStylesheetsOf(new DOMParser().parseFromString(html, "text/html").head);
24
+ const held = siteStylesheetsOf(doc.head);
25
+ const wantedMarkup = new Set(wanted.map((element) => element.outerHTML));
26
+ const heldMarkup = new Set(held.map((element) => element.outerHTML));
27
+ for (const element of held) if (!wantedMarkup.has(element.outerHTML)) element.remove();
28
+ for (const element of wanted) if (!heldMarkup.has(element.outerHTML)) doc.head.appendChild(doc.importNode(element, true));
29
+ }
6
30
 
7
31
  //#endregion
8
- export { EMPTY_PREVIEW_DOCUMENT, isSiteStyle };
32
+ export { EMPTY_PREVIEW_DOCUMENT, isSiteStyle, restyle };
@@ -8,6 +8,7 @@ import { PreviewEditingOwnerContext } from "../preview/previewSelection.js";
8
8
  import { StudioNoticeContext } from "../preview/studioNotice.js";
9
9
  import { ContentEditsContext } from "../../core/editing/contentEdits.js";
10
10
  import { PreviewDocumentContext } from "../runtime/PreviewDocumentContext.js";
11
+ import { restyle } from "../preview/components/previewStyles.js";
11
12
  import { FrameContext } from "../preview/components/Frame.js";
12
13
  import { DerivedPageContent } from "../page/DerivedPageContent.js";
13
14
  import { BlockPeek } from "../preview/components/BlockPeek.js";
@@ -425,7 +426,7 @@ function _temp2() {
425
426
  function _temp() {}
426
427
  /** The page at `pathname`, as the runtime renders it in the renderer's draft. */
427
428
  function FramePage(t0) {
428
- const $ = c(20);
429
+ const $ = c(24);
429
430
  const { camoxApp, codeLoaded, projectUuid, runtimeBasePath, pathname, derivedLayoutId, onRendered } = t0;
430
431
  let t1;
431
432
  if ($[0] !== derivedLayoutId || $[1] !== pathname || $[2] !== projectUuid || $[3] !== runtimeBasePath) {
@@ -449,52 +450,69 @@ function FramePage(t0) {
449
450
  } else t1 = $[4];
450
451
  const input = useRead(t1);
451
452
  if (codeLoaded) React.use(codeLoaded);
452
- const rendered = React.useEffectEvent(onRendered);
453
+ const frameWindow = React.use(FrameContext).window;
453
454
  let t2;
454
- if ($[7] !== rendered) {
455
- t2 = () => rendered();
456
- $[7] = rendered;
457
- $[8] = t2;
458
- } else t2 = $[8];
459
455
  let t3;
460
- if ($[9] !== input) {
461
- t3 = [input];
462
- $[9] = input;
456
+ if ($[7] !== frameWindow || $[8] !== input.previewDocument) {
457
+ t2 = () => {
458
+ if (frameWindow && input.previewDocument) restyle(frameWindow, input.previewDocument);
459
+ };
460
+ t3 = [frameWindow, input.previewDocument];
461
+ $[7] = frameWindow;
462
+ $[8] = input.previewDocument;
463
+ $[9] = t2;
463
464
  $[10] = t3;
464
- } else t3 = $[10];
465
- React.useEffect(t2, t3);
466
- const t4 = input.dehydratedState;
465
+ } else {
466
+ t2 = $[9];
467
+ t3 = $[10];
468
+ }
469
+ React.useLayoutEffect(t2, t3);
470
+ const rendered = React.useEffectEvent(onRendered);
471
+ let t4;
472
+ if ($[11] !== rendered) {
473
+ t4 = () => rendered();
474
+ $[11] = rendered;
475
+ $[12] = t4;
476
+ } else t4 = $[12];
467
477
  let t5;
468
- if ($[11] !== camoxApp || $[12] !== input.derived) {
469
- t5 = input.derived ? /* @__PURE__ */ jsx(DerivedPageContent, {
478
+ if ($[13] !== input) {
479
+ t5 = [input];
480
+ $[13] = input;
481
+ $[14] = t5;
482
+ } else t5 = $[14];
483
+ React.useEffect(t4, t5);
484
+ const t6 = input.dehydratedState;
485
+ let t7;
486
+ if ($[15] !== camoxApp || $[16] !== input.derived) {
487
+ t7 = input.derived ? /* @__PURE__ */ jsx(DerivedPageContent, {
470
488
  camoxApp,
471
489
  derived: input.derived
472
490
  }) : /* @__PURE__ */ jsx(EditablePageContent, {});
473
- $[11] = camoxApp;
474
- $[12] = input.derived;
475
- $[13] = t5;
476
- } else t5 = $[13];
477
- let t6;
478
- if ($[14] !== input.previewDocument || $[15] !== t5) {
479
- t6 = /* @__PURE__ */ jsx(PreviewDocumentContext.Provider, {
491
+ $[15] = camoxApp;
492
+ $[16] = input.derived;
493
+ $[17] = t7;
494
+ } else t7 = $[17];
495
+ let t8;
496
+ if ($[18] !== input.previewDocument || $[19] !== t7) {
497
+ t8 = /* @__PURE__ */ jsx(PreviewDocumentContext.Provider, {
480
498
  value: input.previewDocument,
481
- children: t5
482
- });
483
- $[14] = input.previewDocument;
484
- $[15] = t5;
485
- $[16] = t6;
486
- } else t6 = $[16];
487
- let t7;
488
- if ($[17] !== t4 || $[18] !== t6) {
489
- t7 = /* @__PURE__ */ jsx(HydrationBoundary, {
490
- state: t4,
491
- children: t6
499
+ children: t7
492
500
  });
493
- $[17] = t4;
494
- $[18] = t6;
501
+ $[18] = input.previewDocument;
495
502
  $[19] = t7;
496
- } else t7 = $[19];
497
- return t7;
503
+ $[20] = t8;
504
+ } else t8 = $[20];
505
+ let t9;
506
+ if ($[21] !== t6 || $[22] !== t8) {
507
+ t9 = /* @__PURE__ */ jsx(HydrationBoundary, {
508
+ state: t6,
509
+ children: t8
510
+ });
511
+ $[21] = t6;
512
+ $[22] = t8;
513
+ $[23] = t9;
514
+ } else t9 = $[23];
515
+ return t9;
498
516
  }
499
517
  const messageOf = (error) => error instanceof Error ? error.message : String(error);
500
518
  const ERROR_STYLE = {
@@ -0,0 +1,58 @@
1
+ import { artifactFontLinks, artifactShellHash, artifactUrlDocument } from "../../core/lib/artifact.js";
2
+ import { ARTIFACTS_PATH_PREFIX } from "./runtimeRoutes.js";
3
+
4
+ //#region src/features/runtime/artifactDocuments.ts
5
+ const shells = /* @__PURE__ */ new WeakMap();
6
+ /**
7
+ * The website's artifact shell, computed once per runtime: its font links come from the build's
8
+ * document definition, so they never change while it runs.
9
+ */
10
+ function readArtifactShell(options) {
11
+ let shell = shells.get(options);
12
+ if (!shell) {
13
+ shell = (async () => {
14
+ const fontLinks = artifactFontLinks(await options.getDocument?.());
15
+ return {
16
+ fontLinks,
17
+ hash: await artifactShellHash(fontLinks)
18
+ };
19
+ })();
20
+ shell.catch(() => shells.delete(options));
21
+ shells.set(options, shell);
22
+ }
23
+ return shell;
24
+ }
25
+ const YEAR = 31536e3;
26
+ /**
27
+ * An artifact's document, served at `/_camox/artifacts/<shellHash>/<sourceHash>`: the source the
28
+ * API stores under its hash, inside the website's current shell. It's the same for every visitor,
29
+ * so cookies are ignored. It keeps an opaque origin even when opened top-level, and only a
30
+ * document whose shell matches its URL is cached: one from a previous release still loads.
31
+ */
32
+ async function createArtifactDocumentResponse(pathname, options) {
33
+ const [shellHash, sourceHash, ...rest] = pathname.slice(ARTIFACTS_PATH_PREFIX.length).split("/");
34
+ const { apiUrl, projectUuid } = options;
35
+ if (!apiUrl || !projectUuid) return Response.json({ message: "Camox artifacts are not configured." }, { status: 500 });
36
+ if (!shellHash || !sourceHash || rest.length) return notFound();
37
+ const [shell, stored] = await Promise.all([readArtifactShell(options), fetch(`${apiUrl}/artifact-sources/${encodeURIComponent(projectUuid)}/${encodeURIComponent(sourceHash)}`)]);
38
+ if (stored.status === 404) return notFound();
39
+ if (!stored.ok) return new Response("Artifact source unavailable", {
40
+ status: 502,
41
+ headers: { "Cache-Control": "no-store" }
42
+ });
43
+ return new Response(artifactUrlDocument(await stored.text(), shell.fontLinks), { headers: {
44
+ "Content-Type": "text/html; charset=utf-8",
45
+ "Cache-Control": shellHash === shell.hash ? `public, max-age=${YEAR}, immutable` : "no-store",
46
+ "Content-Security-Policy": "sandbox allow-scripts",
47
+ "X-Robots-Tag": "noindex"
48
+ } });
49
+ }
50
+ function notFound() {
51
+ return new Response("Not found", {
52
+ status: 404,
53
+ headers: { "Cache-Control": "no-store" }
54
+ });
55
+ }
56
+
57
+ //#endregion
58
+ export { createArtifactDocumentResponse, readArtifactShell };
@@ -1,6 +1,40 @@
1
- import { artifactSourceOf } from "../../core/lib/artifact.js";
1
+ import { artifactSourceOf, artifactTitle } from "../../core/lib/artifact.js";
2
2
 
3
3
  //#region src/features/runtime/artifactSources.ts
4
+ function isStringRecord(value) {
5
+ return isPlainObject(value) && Object.values(value).every((hash) => typeof hash === "string");
6
+ }
7
+ /** A copy of a JSON value with every object replaced, deepest first. */
8
+ function mapObjects(value, replace) {
9
+ if (Array.isArray(value)) return value.map((item) => mapObjects(item, replace));
10
+ if (!isPlainObject(value)) return value;
11
+ return replace(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, mapObjects(item, replace)])));
12
+ }
13
+ /**
14
+ * The page's dehydrated state with each artifact source replaced by a reference to the URL its
15
+ * document loads from, `<artifactsPath><sourceHash>`, and its title: public pages load their
16
+ * artifacts by URL, so neither their HTML nor their hydration data carries a source. Every block
17
+ * and repeatable item the API reads names its sources' hashes beside its content.
18
+ */
19
+ function referenceArtifactSources(dehydratedState, artifactsPath) {
20
+ return mapObjects(dehydratedState, (owner) => {
21
+ const { artifactHashes, content } = owner;
22
+ if (!isStringRecord(artifactHashes) || !isPlainObject(content)) return owner;
23
+ const referenced = { ...content };
24
+ for (const [field, hash] of Object.entries(artifactHashes)) {
25
+ const source = content[field];
26
+ if (typeof source !== "string") continue;
27
+ referenced[field] = {
28
+ artifactUrl: `${artifactsPath}${hash}`,
29
+ title: artifactTitle(source)
30
+ };
31
+ }
32
+ return {
33
+ ...owner,
34
+ content: referenced
35
+ };
36
+ });
37
+ }
4
38
  /**
5
39
  * A page's HTML holds each rendered artifact source in its frame's `srcdoc`, so the server leaves
6
40
  * them out of the hydration data, and the client takes them back from the frames before
@@ -84,4 +118,4 @@ function restoreArtifactSources(dehydratedState, root) {
84
118
  }
85
119
 
86
120
  //#endregion
87
- export { omitArtifactSources, restoreArtifactSources };
121
+ export { omitArtifactSources, referenceArtifactSources, restoreArtifactSources };