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
@@ -416,7 +416,7 @@ export declare function createApp({ blocks, lazyBlocks, layouts, collections, do
416
416
  Component: ({ blockData, mode, isFirstBlock, showAddBlockTop, showAddBlockBottom, addBlockAfterPosition }: BlockComponentProps<{
417
417
  [x: string]: unknown;
418
418
  }>) => import("react").JSX.Element;
419
- LayoutItem: ({ collectionId, collection, properties, record, children, fallback }: LayoutItemProps) => string | number | bigint | boolean | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
419
+ LayoutItem: ({ collectionId, collection, properties, record, children, fallback }: LayoutItemProps) => string | number | bigint | boolean | import("react").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | null | undefined;
420
420
  id: string;
421
421
  title: string;
422
422
  description: string;
@@ -858,7 +858,7 @@ export declare function createApp({ blocks, lazyBlocks, layouts, collections, do
858
858
  Component: ({ blockData, mode, isFirstBlock, showAddBlockTop, showAddBlockBottom, addBlockAfterPosition }: BlockComponentProps<{
859
859
  [x: string]: unknown;
860
860
  }>) => import("react").JSX.Element;
861
- LayoutItem: ({ collectionId, collection, properties, record, children, fallback }: LayoutItemProps) => string | number | bigint | boolean | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
861
+ LayoutItem: ({ collectionId, collection, properties, record, children, fallback }: LayoutItemProps) => string | number | bigint | boolean | import("react").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | null | undefined;
862
862
  id: string;
863
863
  title: string;
864
864
  description: string;
@@ -1,4 +1,4 @@
1
- import { artifactFrameProps, artifactStandInProps, isArtifactSource } from "./lib/artifact.js";
1
+ import { artifactFrameProps, artifactStandInProps, isArtifactSource, isArtifactSourceReference } from "./lib/artifact.js";
2
2
  import { artifactData } from "./lib/artifactData.js";
3
3
  import { resolveBlockFields, resolveToMarkdown, resolveToStructuredData, resolveToTitle } from "./lib/contentType.js";
4
4
  import { useProjectUuid } from "../lib/auth.js";
@@ -190,9 +190,13 @@ function createViewBlock(options) {
190
190
  size: useFieldSchema(name)?.size,
191
191
  navigate: useNavigate()
192
192
  });
193
- const initialData = useInitialArtifactData(value, data);
193
+ const initialData = useInitialArtifactData(isArtifactSourceReference(value) ? value.artifactUrl : value, data);
194
194
  const fontLinks = useOptionalCamoxApp()?.getArtifactFontLinks();
195
195
  const runs = React.use(ArtifactsRunContext);
196
+ if (isArtifactSourceReference(value)) return children({
197
+ ...artifactFrameProps(value, { data: initialData }),
198
+ ...hostProps
199
+ });
196
200
  if (!isArtifactSource(value)) return null;
197
201
  return children({
198
202
  ...runs ? artifactFrameProps(value, {
@@ -236,7 +236,7 @@ declare function createLayoutDefinition(options: AnyLayoutOptions): {
236
236
  Item: {
237
237
  ({ children }: {
238
238
  children: (item: any) => React.ReactNode;
239
- }): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
239
+ }): string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
240
240
  displayName: string;
241
241
  };
242
242
  useData(): unknown;
@@ -1694,7 +1694,7 @@ export declare function createEditableBlock<TSchemaShape extends Record<string,
1694
1694
  params: unknown[] & [];
1695
1695
  })["static"]; }, Exclude<keyof TSchemaShape, { [K_1 in keyof TSchemaShape]: TSchemaShape[K_1] extends import("@sinclair/typebox").TReadonly<TSchema> ? TSchemaShape[K_1] extends import("@sinclair/typebox").TOptional<TSchemaShape[K_1]> ? K_1 : never : never; }[keyof TSchemaShape] | { [K_2 in keyof TSchemaShape]: TSchemaShape[K_2] extends import("@sinclair/typebox").TReadonly<TSchema> ? TSchemaShape[K_2] extends import("@sinclair/typebox").TOptional<TSchemaShape[K_2]> ? never : K_2 : never; }[keyof TSchemaShape] | { [K_3 in keyof TSchemaShape]: TSchemaShape[K_3] extends import("@sinclair/typebox").TOptional<TSchema> ? TSchemaShape[K_3] extends import("@sinclair/typebox").TReadonly<TSchemaShape[K_3]> ? never : K_3 : never; }[keyof TSchemaShape]>>>>>) => React.JSX.Element;
1696
1696
  /** Renders a record page's record; see `BlockEditingRuntime.renderLayoutItem`. */
1697
- LayoutItem: ({ collectionId, collection, properties, record, children, fallback }: LayoutItemProps) => string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
1697
+ LayoutItem: ({ collectionId, collection, properties, record, children, fallback }: LayoutItemProps) => string | number | bigint | boolean | React.JSX.Element | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | null | undefined;
1698
1698
  id: string;
1699
1699
  title: string;
1700
1700
  description: string;
@@ -3,14 +3,23 @@ import "./artifactData.js";
3
3
  import * as React from "react";
4
4
  import "@camox/api-contract";
5
5
  //#region src/core/lib/artifact.d.ts
6
- /** The frame attributes of an artifact source. */
7
- export type ArtifactFrameProps = {
8
- srcDoc: string;
6
+ /** The frame attributes an artifact's frame has, whichever way it loads its document. */
7
+ type ArtifactFrameAttributes = {
9
8
  sandbox: string;
10
9
  allow: string;
11
10
  title: string;
12
11
  loading: "lazy";
13
12
  };
13
+ /** The frame attributes of an artifact whose document is inlined. */
14
+ export type InlineArtifactFrameProps = ArtifactFrameAttributes & {
15
+ srcDoc: string;
16
+ src?: never;
17
+ };
18
+ /** The frame attributes of an artifact: its document inlined, or loaded by URL. */
19
+ export type ArtifactFrameProps = InlineArtifactFrameProps | (ArtifactFrameAttributes & {
20
+ src: string;
21
+ srcDoc?: never;
22
+ });
14
23
  /** What `block.Artifact` passes to its render prop, to spread onto an `<iframe>`. */
15
24
  export type ArtifactRenderProps = ArtifactFrameProps & {
16
25
  /** Connects the frame to the `camox` runtime inside it. */
@@ -18,4 +27,5 @@ export type ArtifactRenderProps = ArtifactFrameProps & {
18
27
  /** For `size: "content"`, the height the artifact's content needs, once it reports it. */
19
28
  style?: React.CSSProperties;
20
29
  };
21
- //#endregion
30
+ //#endregion
31
+ export {};
@@ -1,8 +1,14 @@
1
1
  import { escapeAttribute } from "../../lib/html.js";
2
2
  import { ARTIFACT_RUNTIME } from "./artifactRuntime.js";
3
- import { artifactTitle } from "@camox/api-contract";
3
+ import { artifactSourceHash, artifactTitle } from "@camox/api-contract";
4
4
 
5
5
  //#region src/core/lib/artifact.ts
6
+ /** Whether a stored artifact value was replaced by a reference to its source. */
7
+ function isArtifactSourceReference(value) {
8
+ if (typeof value !== "object" || value === null) return false;
9
+ const { artifactUrl, title } = value;
10
+ return typeof artifactUrl === "string" && typeof title === "string";
11
+ }
6
12
  const FONT_LINK_RELS = /* @__PURE__ */ new Set(["stylesheet", "preconnect"]);
7
13
  const UNHEAD_TAG_OPTIONS = /* @__PURE__ */ new Set([
8
14
  "key",
@@ -32,6 +38,15 @@ function artifactDataScript(data) {
32
38
  return `${DATA_SCRIPT_OPEN}${json}<\/script>`;
33
39
  }
34
40
  /**
41
+ * The fragment, `#` included, that carries the starting Artifact Data in the URL of a frame that
42
+ * loads its artifact by URL instead of `srcdoc`. The runtime decodes it as `camox.data`. Fragments
43
+ * never reach the server, so one cached document serves every placement. Empty data needs none.
44
+ */
45
+ function artifactDataFragment(data) {
46
+ const json = JSON.stringify(data);
47
+ return json === "{}" ? "" : `#${encodeURIComponent(json)}`;
48
+ }
49
+ /**
35
50
  * The stylesheet and preconnect links of the project's document definition, as HTML, so artifacts
36
51
  * use the website's fonts from their first render. Nothing else from the document's head: its
37
52
  * title, meta, scripts and icons belong to the website, not to the artifact.
@@ -47,12 +62,35 @@ function artifactFontLinks(document) {
47
62
  }).map(renderLink).join("");
48
63
  }
49
64
  /**
50
- * The document an artifact frame loads: the `camox` runtime, the website's font links, the initial
51
- * Artifact Data, then the stored source. The doctype comes first so the runtime's script never
52
- * puts the page in quirks mode; the source's own doctype, if any, is then ignored by the parser.
65
+ * What every artifact's document holds before its source: the `camox` runtime, then the website's
66
+ * font links. The doctype comes first so the runtime's script never puts the page in quirks mode;
67
+ * the source's own doctype, if any, is then ignored by the parser.
68
+ */
69
+ function artifactShell(fontLinks) {
70
+ return `<!doctype html><script>${ARTIFACT_RUNTIME}<\/script>${fontLinks}`;
71
+ }
72
+ /**
73
+ * The hash of the shell around every artifact source, which names it in the URLs public pages
74
+ * load artifacts from. It changes only with the runtime or the website's font links, so a release
75
+ * that changes neither keeps every artifact cached.
76
+ */
77
+ function artifactShellHash(fontLinks) {
78
+ return artifactSourceHash(artifactShell(fontLinks));
79
+ }
80
+ /**
81
+ * The document an inline (`srcdoc`) artifact frame loads: the shell, the initial Artifact Data,
82
+ * then the stored source.
53
83
  */
54
84
  function artifactDocument(source, { fontLinks = "", data = {} }) {
55
- return `<!doctype html>${`<script>${ARTIFACT_RUNTIME}<\/script>${fontLinks}${artifactDataScript(data)}`}${source}`;
85
+ return `${artifactShell(fontLinks)}${artifactDataScript(data)}${source}`;
86
+ }
87
+ /**
88
+ * The document served at an artifact's URL: the shell, then the stored source. No Artifact Data:
89
+ * the frame's URL fragment carries it (`artifactDataFragment`), so one cached document serves
90
+ * every placement, and an inline data script would win over the fragment.
91
+ */
92
+ function artifactUrlDocument(source, fontLinks) {
93
+ return `${artifactShell(fontLinks)}${source}`;
56
94
  }
57
95
  /**
58
96
  * The artifact source inside an artifact's document, the reverse of `artifactDocument`, or `null`
@@ -60,7 +98,7 @@ function artifactDocument(source, { fontLinks = "", data = {} }) {
60
98
  * JSON escapes it too, so the first `<\/script>` after the data's opening tag ends the data.
61
99
  */
62
100
  function artifactSourceOf(document) {
63
- const head = `<!doctype html><script>${ARTIFACT_RUNTIME}<\/script>`;
101
+ const head = artifactShell("");
64
102
  if (!document.startsWith(head)) return null;
65
103
  const data = document.indexOf(DATA_SCRIPT_OPEN, head.length);
66
104
  if (data < 0 || document.slice(head.length, data).includes("<script")) return null;
@@ -68,19 +106,22 @@ function artifactSourceOf(document) {
68
106
  if (end < 0) return null;
69
107
  return document.slice(end + 9);
70
108
  }
71
- /**
72
- * The frame props for an artifact source, with its initial Artifact Data and the website's font
73
- * links. This getter is the boundary that hides how the source reaches the frame (inline `srcdoc`
74
- * today), so blocks never depend on it.
75
- */
76
- function artifactFrameProps(source, options = {}) {
77
- return {
78
- srcDoc: artifactDocument(source, options),
109
+ function artifactFrameProps(artifact, options = {}) {
110
+ const attributes = {
79
111
  sandbox: "allow-scripts",
80
112
  allow: "fullscreen; clipboard-write",
81
- title: artifactTitle(source),
82
113
  loading: "lazy"
83
114
  };
115
+ if (typeof artifact === "string") return {
116
+ srcDoc: artifactDocument(artifact, options),
117
+ ...attributes,
118
+ title: artifactTitle(artifact)
119
+ };
120
+ return {
121
+ src: `${artifact.artifactUrl}${artifactDataFragment(options.data ?? {})}`,
122
+ ...attributes,
123
+ title: artifact.title
124
+ };
84
125
  }
85
126
  const PLACEHOLDER_SOURCE = `<!doctype html>
86
127
  <html>
@@ -151,4 +192,4 @@ function isArtifactSource(value) {
151
192
  }
152
193
 
153
194
  //#endregion
154
- export { artifactFontLinks, artifactFrameProps, artifactPlaceholderProps, artifactSourceOf, artifactStandInProps, artifactTitle, isArtifactSource };
195
+ export { artifactDataFragment, artifactFontLinks, artifactFrameProps, artifactPlaceholderProps, artifactShellHash, artifactSourceOf, artifactStandInProps, artifactTitle, artifactUrlDocument, isArtifactSource, isArtifactSourceReference };
@@ -13,7 +13,9 @@
13
13
  * change. The host converts the colors, so the runtime that every frame inlines stays small.
14
14
  * - host → frame: `data` with the latest Artifact Data. The initial data is inlined in the
15
15
  * `srcDoc` as a `<script type="application/json" data-camox-data>` after the runtime and the
16
- * font links, so it is read lazily, on first access.
16
+ * font links, or, for a frame loaded by URL, encoded in the URL's fragment (see
17
+ * `artifactDataFragment`). It is read lazily, on first access, but the fragment is captured as
18
+ * the runtime starts, before the artifact source can change it.
17
19
  * - runtime → page: `observe-viewport` with `observe`, when the artifact's first `onViewport`
18
20
  * subscription starts or its last one ends. While observed, the host sends `viewport` with
19
21
  * `progress` and `visibleRatio` whenever they change.
@@ -30,7 +32,7 @@
30
32
  * `plugins/minifyArtifactRuntime.ts`), so it must stay one template literal without
31
33
  * interpolations or backslashes.
32
34
  */
33
- const ARTIFACT_RUNTIME = "(()=>{\"use strict\";let e=window.parent,t=null,n=null,r,i,a=new Set,o=()=>{if(i!==void 0)return r;let e=document.querySelector(`script[data-camox-data]`);try{r=e?JSON.parse(e.textContent):{}}catch{r={}}return i=JSON.stringify(r),r},s=(()=>{let e=new Set,t=[],n=Object.freeze({tokens:Object.freeze({}),colors:Object.freeze({})}),r=document.createElement(`style`);r.textContent=`body { visibility: hidden !important; }`,document.head.append(r);let i=()=>r.remove();setTimeout(i,300);let a=e=>Number.isFinite(e)?Math.min(255,Math.max(0,Math.round(e))):0;return{get current(){return n},receive(r){let o={},s={},c=r.colors||{};for(let[e,t]of Object.entries(r.tokens||{})){if(!e.startsWith(`--`)||typeof t!=`string`)continue;o[e]=t;let n=Object.hasOwn(c,e)?c[e]:null;n&&typeof n==`object`&&(s[e]=Object.freeze({r:a(n.r),g:a(n.g),b:a(n.b)}))}let l=document.documentElement.style;for(let e of t)Object.hasOwn(o,e)||l.removeProperty(e);for(let[e,t]of Object.entries(o))l.setProperty(e,t);t=Object.keys(o),i(),n=Object.freeze({tokens:Object.freeze(o),colors:Object.freeze(s)});for(let t of[...e])t(n)},subscribe(t){return e.add(t),()=>{e.delete(t)}}}})(),c=new Set,l=null,u=null,d=null,f=()=>{let e=Math.ceil(document.documentElement.getBoundingClientRect().height);e!==d&&(d=e,p(`height`,{height:d}))},p=(n,r)=>{t&&e.postMessage({...r,camox:n,channel:t},`*`)},m=()=>p(`observe-viewport`,{observe:c.size>0}),h={studio(e){let t=e.studio;n=t?Object.freeze({editing:!!t.editing,canvas:!!t.canvas}):null},data(e){o();let t=JSON.stringify(e.data??{});if(t!==i){r=JSON.parse(t),i=t;for(let e of[...a])e(r)}},theme:s.receive,measure(){d=null,u||(u=new ResizeObserver(f),u.observe(document.documentElement)),f()},viewport(e){let{progress:t,visibleRatio:n}=e;if(typeof t==`number`&&typeof n==`number`){l=Object.freeze({progress:t,visibleRatio:n}),document.documentElement.style.setProperty(`--camox-progress`,String(t));for(let e of c)e(l)}}};window.addEventListener(`message`,e=>{let n=e.data;if(n&&typeof n.camox==`string`){if(n.camox===`connect`){if(typeof n.channel!=`string`||n.channel===t)return;t=n.channel,p(`ready`),c.size>0&&m()}else t&&n.channel===t&&Object.hasOwn(h,n.camox)&&h[n.camox](n)}});let g=Object.freeze({get studio(){return n},get data(){return o()},onData(e){return a.add(e),()=>{a.delete(e)}},get theme(){return s.current},onTheme:s.subscribe,navigate(e){p(`navigate`,{href:String(e)})},onViewport(e){if(typeof e!=`function`)throw TypeError(`camox.onViewport needs a callback`);let t=t=>e(t);return c.add(t),c.size===1?m():l&&queueMicrotask(()=>c.has(t)&&e(l)),()=>{c.delete(t)&&c.size===0&&m()}}});Object.defineProperty(window,\"camox\",{value:g,enumerable:!0}),e!==window&&e.postMessage({camox:`hello`},`*`)})();";
35
+ const ARTIFACT_RUNTIME = "(()=>{\"use strict\";let e=window.parent,t=null,n=null,r,i,a=new Set,o=location.hash.slice(1),s=()=>{if(i!==void 0)return r;let e=document.querySelector(`script[data-camox-data]`);try{r=JSON.parse(e?e.textContent:decodeURIComponent(o)),(!r||typeof r!=`object`||Array.isArray(r))&&(r={})}catch{r={}}return i=JSON.stringify(r),r},c=(()=>{let e=new Set,t=[],n=Object.freeze({tokens:Object.freeze({}),colors:Object.freeze({})}),r=document.createElement(`style`);r.textContent=`body { visibility: hidden !important; }`,document.head.append(r);let i=()=>r.remove();setTimeout(i,300);let a=e=>Number.isFinite(e)?Math.min(255,Math.max(0,Math.round(e))):0;return{get current(){return n},receive(r){let o={},s={},c=r.colors||{};for(let[e,t]of Object.entries(r.tokens||{})){if(!e.startsWith(`--`)||typeof t!=`string`)continue;o[e]=t;let n=Object.hasOwn(c,e)?c[e]:null;n&&typeof n==`object`&&(s[e]=Object.freeze({r:a(n.r),g:a(n.g),b:a(n.b)}))}let l=document.documentElement.style;for(let e of t)Object.hasOwn(o,e)||l.removeProperty(e);for(let[e,t]of Object.entries(o))l.setProperty(e,t);t=Object.keys(o),i(),n=Object.freeze({tokens:Object.freeze(o),colors:Object.freeze(s)});for(let t of[...e])t(n)},subscribe(t){return e.add(t),()=>{e.delete(t)}}}})(),l=new Set,u=null,d=null,f=null,p=()=>{let e=Math.ceil(document.documentElement.getBoundingClientRect().height);e!==f&&(f=e,m(`height`,{height:f}))},m=(n,r)=>{t&&e.postMessage({...r,camox:n,channel:t},`*`)},h=()=>m(`observe-viewport`,{observe:l.size>0}),g={studio(e){let t=e.studio;n=t?Object.freeze({editing:!!t.editing,canvas:!!t.canvas}):null},data(e){s();let t=JSON.stringify(e.data??{});if(t!==i){r=JSON.parse(t),i=t;for(let e of[...a])e(r)}},theme:c.receive,measure(){f=null,d||(d=new ResizeObserver(p),d.observe(document.documentElement)),p()},viewport(e){let{progress:t,visibleRatio:n}=e;if(typeof t==`number`&&typeof n==`number`){u=Object.freeze({progress:t,visibleRatio:n}),document.documentElement.style.setProperty(`--camox-progress`,String(t));for(let e of l)e(u)}}};window.addEventListener(`message`,e=>{let n=e.data;if(n&&typeof n.camox==`string`){if(n.camox===`connect`){if(typeof n.channel!=`string`||n.channel===t)return;t=n.channel,m(`ready`),l.size>0&&h()}else t&&n.channel===t&&Object.hasOwn(g,n.camox)&&g[n.camox](n)}});let _=Object.freeze({get studio(){return n},get data(){return s()},onData(e){return a.add(e),()=>{a.delete(e)}},get theme(){return c.current},onTheme:c.subscribe,navigate(e){m(`navigate`,{href:String(e)})},onViewport(e){if(typeof e!=`function`)throw TypeError(`camox.onViewport needs a callback`);let t=t=>e(t);return l.add(t),l.size===1?h():u&&queueMicrotask(()=>l.has(t)&&e(u)),()=>{l.delete(t)&&l.size===0&&h()}}});Object.defineProperty(window,\"camox\",{value:_,enumerable:!0}),e!==window&&e.postMessage({camox:`hello`},`*`)})();";
34
36
 
35
37
  //#endregion
36
38
  export { ARTIFACT_RUNTIME };
@@ -39,7 +39,7 @@ function CanvasPageHeader({ page, pathname, selected, onSelect, onHoverChange, o
39
39
  }
40
40
  };
41
41
  const path = pathname ?? (page.kind === "derived" ? page.pattern : void 0);
42
- const pathClassName = "text-muted-foreground w-full min-w-0 truncate text-left text-xs font-normal";
42
+ const pathClassName = "text-muted-foreground max-w-full min-w-0 truncate text-left text-xs font-normal";
43
43
  const focusClassName = "focus-visible:ring-ring outline-none focus-visible:ring-2";
44
44
  const name = draft !== null ? /* @__PURE__ */ jsx("input", {
45
45
  "aria-label": "Page nickname",
@@ -4,6 +4,16 @@ import { EMPTY_PREVIEW_DOCUMENT, isSiteStyle } from "../preview/components/previ
4
4
  //#region src/features/canvas/canvasFrameDocument.ts
5
5
  /** Freezes CSS animations, including ones that start while the frame is paused. */
6
6
  const PAUSED_ANIMATIONS_STYLE = `html[${PREVIEW_PAUSED_ATTRIBUTE}] *, html[${PREVIEW_PAUSED_ATTRIBUTE}] *::before, html[${PREVIEW_PAUSED_ATTRIBUTE}] *::after { animation-play-state: paused !important; }`;
7
+ /**
8
+ * A style of the studio's own for a frame's document, marked so that giving the frame a site
9
+ * document's stylesheets (`restyle`) leaves it.
10
+ */
11
+ function studioStyle(doc, css) {
12
+ const style = doc.createElement("style");
13
+ style.setAttribute("data-camox-studio", "");
14
+ style.textContent = css;
15
+ return style;
16
+ }
7
17
  /** Set by the camera on elements that hold Canvas frames: see `canvasBlockOverlap`. */
8
18
  const BLOCK_OVERLAP_ATTRIBUTE = "data-canvas-block-overlap";
9
19
  const BLOCK = "[data-camox-viewport-block]";
@@ -93,8 +103,7 @@ function createCanvasDocument(previewDocument, href) {
93
103
  doc.querySelector("[data-camox-preview-root]")?.replaceChildren();
94
104
  doc.documentElement.style.setProperty("overscroll-behavior", "none");
95
105
  doc.body.style.setProperty("overscroll-behavior", "none");
96
- const paused = doc.createElement("style");
97
- paused.textContent = PAUSED_ANIMATIONS_STYLE;
106
+ const paused = studioStyle(doc, PAUSED_ANIMATIONS_STYLE);
98
107
  doc.head.appendChild(paused);
99
108
  return `<!doctype html>${doc.documentElement.outerHTML}`;
100
109
  }
@@ -106,8 +115,7 @@ function createCanvasDocument(previewDocument, href) {
106
115
  */
107
116
  function adoptCanvasDocument(iframe, root, viewportHeight) {
108
117
  const doc = root.ownerDocument;
109
- const paused = doc.createElement("style");
110
- paused.textContent = PAUSED_ANIMATIONS_STYLE;
118
+ const paused = studioStyle(doc, PAUSED_ANIMATIONS_STYLE);
111
119
  doc.head.appendChild(paused);
112
120
  const restored = [doc.documentElement, doc.body].map((element) => ({
113
121
  element,
@@ -199,8 +207,7 @@ function observeCanvasDocument(iframe, root, viewportHeight, suspended = () => f
199
207
  doc.documentElement.style.setProperty("--camox-viewport-height", `${baseline}px`);
200
208
  doc.documentElement.style.setProperty("overflow", "hidden", "important");
201
209
  doc.body.style.setProperty("overflow", "hidden", "important");
202
- const overlapStyle = doc.createElement("style");
203
- overlapStyle.textContent = BLOCK_OVERLAP_STYLE;
210
+ const overlapStyle = studioStyle(doc, BLOCK_OVERLAP_STYLE);
204
211
  doc.head.appendChild(overlapStyle);
205
212
  const followOverlap = () => {
206
213
  const overlap = iframe.closest(`[${BLOCK_OVERLAP_ATTRIBUTE}]`);
@@ -5,7 +5,7 @@ import { PageViewportContext } from "../../runtime/pageViewport.js";
5
5
  import { PreviewDocumentContext } from "../../runtime/PreviewDocumentContext.js";
6
6
  import { ViewportContinuityContext, capturePreviewAnchor, restorePreviewAnchor } from "../viewportContinuity.js";
7
7
  import { PreviewActivationContext } from "./PreviewActivation.js";
8
- import { EMPTY_PREVIEW_DOCUMENT, isSiteStyle } from "./previewStyles.js";
8
+ import { EMPTY_PREVIEW_DOCUMENT, isSiteStyle, restyle } from "./previewStyles.js";
9
9
  import { c } from "react/compiler-runtime";
10
10
  import * as React from "react";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -21,23 +21,6 @@ function useFrame() {
21
21
  if (!context) throw new Error("useFrame must be used within a Frame");
22
22
  return context;
23
23
  }
24
- /** The stylesheets of a document's head that are the site's: not the studio's. */
25
- const SITE_STYLESHEETS = "link[rel~=\"stylesheet\"], style";
26
- const siteStylesheetsOf = (head) => Array.from(head.querySelectorAll(SITE_STYLESHEETS)).filter((element) => element.parentElement === head && isSiteStyle(element));
27
- /**
28
- * Gives the frame's document the site stylesheets of `html`, a site document: the ones it no
29
- * longer has go, and its new ones are added, the rest of the frame left as it is.
30
- */
31
- function restyle(frame, html) {
32
- const doc = frame.document;
33
- const { DOMParser } = frame;
34
- const wanted = siteStylesheetsOf(new DOMParser().parseFromString(html, "text/html").head);
35
- const held = siteStylesheetsOf(doc.head);
36
- const wantedMarkup = new Set(wanted.map((element) => element.outerHTML));
37
- const heldMarkup = new Set(held.map((element) => element.outerHTML));
38
- for (const element of held) if (!wantedMarkup.has(element.outerHTML)) element.remove();
39
- for (const element of wanted) if (!heldMarkup.has(element.outerHTML)) doc.head.appendChild(doc.importNode(element, true));
40
- }
41
24
  function ClearServerMarkup(t0) {
42
25
  const $ = c(4);
43
26
  const { nodes } = t0;