camox 0.51.0 → 0.52.2

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 (91) hide show
  1. package/dist/core/createApp.d.ts +1 -0
  2. package/dist/core/createApp.js +1 -0
  3. package/dist/core/editing/contentEdits.js +78 -0
  4. package/dist/core/editing/createEditableBlock.js +277 -311
  5. package/dist/core/lib/contentType.js +2 -2
  6. package/dist/core/recordItem.js +3 -3
  7. package/dist/features/canvas/CamoxCanvas.js +15 -15
  8. package/dist/features/canvas/CanvasLoadingScreen.js +15 -11
  9. package/dist/features/canvas/CanvasPageFrame.js +81 -87
  10. package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -1
  11. package/dist/features/canvas/DerivedPagePathInput.js +12 -12
  12. package/dist/features/canvas/canvasOverlayGeometry.js +1 -1
  13. package/dist/features/canvas/canvasStore.js +2 -2
  14. package/dist/features/canvas/sidebar/CuratedPageInspector.js +1 -1
  15. package/dist/features/canvas/sidebar/LayoutPageBlocks.js +23 -31
  16. package/dist/features/canvas/useCanvasCamera.js +1 -1
  17. package/dist/features/canvas/useDiscoveredPreviewPaths.js +16 -9
  18. package/dist/features/page/CuratedPageContent.js +1 -1
  19. package/dist/features/preview/CamoxPreview.d.ts +8 -4
  20. package/dist/features/preview/CamoxPreview.js +257 -442
  21. package/dist/features/preview/EditablePageContent.js +244 -2
  22. package/dist/features/preview/EditablePageExperience.js +2 -4
  23. package/dist/features/preview/components/AddBlockSheet.js +55 -15
  24. package/dist/features/preview/components/BlockActionsPopover.js +12 -16
  25. package/dist/features/preview/components/BlockPeek.js +66 -0
  26. package/dist/features/preview/components/BlockThumbnail.js +134 -119
  27. package/dist/features/preview/components/CanvasPreview.js +1 -1
  28. package/dist/features/preview/components/CreatePageModal.js +150 -156
  29. package/dist/features/preview/components/Frame.js +27 -4
  30. package/dist/features/preview/components/MobilePreviewDrawer.js +1 -1
  31. package/dist/features/preview/components/PageInfo.js +161 -126
  32. package/dist/features/preview/components/PagePicker.js +2 -2
  33. package/dist/features/preview/components/PageTree.js +174 -190
  34. package/dist/features/preview/components/PreviewPanel.js +3 -3
  35. package/dist/features/preview/components/ReferenceFieldEditor.js +4 -4
  36. package/dist/features/preview/components/ReferenceListFieldEditor.js +4 -4
  37. package/dist/features/preview/components/RemovedBlockPlaceholder.js +37 -70
  38. package/dist/features/preview/components/RepeatableItemsList.js +1 -1
  39. package/dist/features/preview/components/SelectionEditor.js +248 -256
  40. package/dist/features/preview/components/StudioEditingFallback.js +22 -26
  41. package/dist/features/preview/components/useFieldWriter.js +19 -19
  42. package/dist/features/preview/components/useRecordView.js +105 -147
  43. package/dist/features/preview/components/useRepeatableItemActions.js +0 -2
  44. package/dist/features/preview/pageRecord.js +30 -24
  45. package/dist/features/preview/previewStore.js +2 -0
  46. package/dist/features/preview/studioNotice.js +20 -0
  47. package/dist/features/provider/AuthenticatedCamoxProvider.js +46 -30
  48. package/dist/features/renderer/RendererApp.js +542 -0
  49. package/dist/features/renderer/RendererFrames.js +450 -0
  50. package/dist/features/renderer/definitionAnswers.js +26 -0
  51. package/dist/features/renderer/protocol.js +35 -0
  52. package/dist/features/renderer/rendererHost.js +249 -0
  53. package/dist/features/renderer/studioConnection.js +186 -0
  54. package/dist/features/routes/pageRuntime.js +7 -2
  55. package/dist/features/runtime/RuntimeChrome.js +2 -5
  56. package/dist/features/runtime/assetPrefix.js +14 -0
  57. package/dist/features/runtime/breadcrumbs.js +1 -1
  58. package/dist/features/runtime/editingFallback.js +4 -8
  59. package/dist/features/runtime/livePages.js +1 -1
  60. package/dist/features/runtime/pageApp.js +57 -83
  61. package/dist/features/runtime/pageClient.js +1 -1
  62. package/dist/features/runtime/pageNavigation.js +3 -2
  63. package/dist/features/runtime/publicCache.js +15 -3
  64. package/dist/features/runtime/publishingDraft.js +23 -0
  65. package/dist/features/runtime/rendererClient.d.ts +8 -0
  66. package/dist/features/runtime/rendererClient.js +77 -0
  67. package/dist/features/runtime/runtime.d.ts +9 -0
  68. package/dist/features/runtime/runtime.js +36 -17
  69. package/dist/features/runtime/runtimeRoutes.d.ts +2 -0
  70. package/dist/features/runtime/runtimeRoutes.js +2 -0
  71. package/dist/features/runtime/worker.d.ts +10 -1
  72. package/dist/features/runtime/worker.js +20 -7
  73. package/dist/features/studio/FollowAgentProvider.js +10 -10
  74. package/dist/features/studio/components/ProjectMenu.js +1 -1
  75. package/dist/features/studio/components/ReviewDraftSheet.js +242 -138
  76. package/dist/features/studio/components/ThreadMessages.js +100 -65
  77. package/dist/features/studio/components/UserButton.js +1 -1
  78. package/dist/features/vite/definitionsSync.js +1 -1
  79. package/dist/features/vite/hostedChecks.js +127 -0
  80. package/dist/features/vite/runtimeDev.js +26 -0
  81. package/dist/features/vite/vite.js +33 -5
  82. package/dist/hooks/use-page-destinations.js +10 -10
  83. package/dist/lib/revisions.js +20 -0
  84. package/dist/lib/studio-definitions.js +160 -0
  85. package/dist/lib/studio-reads.js +65 -21
  86. package/dist/lib/use-project-room.js +16 -10
  87. package/dist/package.js +1 -1
  88. package/dist/studio.css +1 -1
  89. package/package.json +8 -4
  90. package/skills/camox/SKILL.md +4 -0
  91. package/skills/camox/references/artifacts.md +1 -1
@@ -1,6 +1,5 @@
1
1
  import { useAuthState, useSignInRedirect } from "../../lib/auth.js";
2
2
  import { DraftScopeContext } from "../../lib/draft-scope.js";
3
- import { DerivedPageContent } from "../page/DerivedPageContent.js";
4
3
  import { useSignInShortcut } from "../provider/signInShortcut.js";
5
4
  import { PreviewDocumentContext } from "./PreviewDocumentContext.js";
6
5
  import { PreviewActivation } from "../preview/components/PreviewActivation.js";
@@ -8,6 +7,7 @@ import { Frame } from "../preview/components/Frame.js";
8
7
  import { PreviewSignInPage } from "../provider/PreviewSignInPage.js";
9
8
  import { CoreCamoxProvider } from "../provider/CoreCamoxProvider.js";
10
9
  import { CuratedPageContent } from "../page/CuratedPageContent.js";
10
+ import { DerivedPageContent } from "../page/DerivedPageContent.js";
11
11
  import { EditingFallbackSlot } from "./editingFallback.js";
12
12
  import { PageNavigationProvider } from "./pageNavigation.js";
13
13
  import { RuntimeChromeSlot } from "./runtimeChromeLoader.js";
@@ -97,11 +97,6 @@ function PageApp(t0) {
97
97
  return t6;
98
98
  }
99
99
  const LazyEditablePageExperience = React.lazy(() => import("../preview/EditablePageExperience.js").then((module) => ({ default: module.EditablePageExperience })));
100
- /** Editing lists and adds any block, so it waits for every definition, not only the page's. */
101
- function AllBlocks({ camoxApp, children }) {
102
- React.use(camoxApp.loadAllBlocks());
103
- return children;
104
- }
105
100
  var EditingActivationBoundary = class extends React.Component {
106
101
  state = { failed: false };
107
102
  static getDerivedStateFromError() {
@@ -116,7 +111,7 @@ var EditingActivationBoundary = class extends React.Component {
116
111
  };
117
112
  const subscribeToHydration = () => () => {};
118
113
  function PageExperience(t0) {
119
- const $ = c(38);
114
+ const $ = c(31);
120
115
  const { camoxApp, input, queryClient, studioDocument } = t0;
121
116
  const { isAuthenticated, isLoading } = useAuthState();
122
117
  const hasHydrated = React.useSyncExternalStore(subscribeToHydration, _temp, _temp2);
@@ -175,96 +170,75 @@ function PageExperience(t0) {
175
170
  $[13] = t5;
176
171
  } else t5 = $[13];
177
172
  const isolatedPage = t5;
178
- let t6;
179
- if ($[14] !== input.routeKind || $[15] !== isolatedPage) {
180
- t6 = input.routeKind ? /* @__PURE__ */ jsx(StudioLoadError, {}) : isolatedPage;
181
- $[14] = input.routeKind;
182
- $[15] = isolatedPage;
183
- $[16] = t6;
184
- } else t6 = $[16];
185
- const fallback = t6;
186
173
  if (isAuthenticated) {
187
- let t7;
188
- if ($[17] !== camoxApp || $[18] !== hasHydrated || $[19] !== input || $[20] !== queryClient) {
189
- t7 = hasHydrated ? /* @__PURE__ */ jsx(AllBlocks, {
174
+ let t6;
175
+ if ($[14] !== camoxApp || $[15] !== hasHydrated || $[16] !== input || $[17] !== queryClient) {
176
+ t6 = hasHydrated ? /* @__PURE__ */ jsx(LazyEditablePageExperience, {
190
177
  camoxApp,
191
- children: /* @__PURE__ */ jsx(LazyEditablePageExperience, {
192
- camoxApp,
193
- input,
194
- queryClient
195
- })
178
+ input,
179
+ queryClient
196
180
  }) : null;
197
- $[17] = camoxApp;
198
- $[18] = hasHydrated;
199
- $[19] = input;
200
- $[20] = queryClient;
201
- $[21] = t7;
202
- } else t7 = $[21];
203
- const editor = t7;
204
- const t8 = input.routeKind ? loading : isolatedPage;
181
+ $[14] = camoxApp;
182
+ $[15] = hasHydrated;
183
+ $[16] = input;
184
+ $[17] = queryClient;
185
+ $[18] = t6;
186
+ } else t6 = $[18];
187
+ const editor = t6;
188
+ const t7 = input.routeKind ? loading : studioDocument ? null : isolatedPage;
189
+ let t8;
190
+ if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
191
+ t8 = /* @__PURE__ */ jsx(StudioLoadError, {});
192
+ $[19] = t8;
193
+ } else t8 = $[19];
205
194
  let t9;
206
- if ($[22] !== editor || $[23] !== input.derived || $[24] !== input.previewDocument || $[25] !== input.routeKind || $[26] !== readOnlyPage) {
207
- t9 = input.routeKind ? /* @__PURE__ */ jsx(React.Suspense, {
208
- fallback: loading,
209
- children: editor ?? loading
210
- }) : /* @__PURE__ */ jsx(PreviewActivation, {
211
- fallback: /* @__PURE__ */ jsx(EditingFallbackSlot, {
212
- layoutPage: !!input.derived,
213
- children: /* @__PURE__ */ jsx(Frame, {
214
- serverOnly: !!input.previewDocument,
215
- children: readOnlyPage
216
- })
217
- }),
218
- children: editor
195
+ if ($[20] !== editor || $[21] !== input.derived || $[22] !== input.routeKind) {
196
+ t9 = /* @__PURE__ */ jsx(EditingActivationBoundary, {
197
+ fallback: t8,
198
+ children: input.routeKind ? /* @__PURE__ */ jsx(React.Suspense, {
199
+ fallback: loading,
200
+ children: editor ?? loading
201
+ }) : /* @__PURE__ */ jsx(PreviewActivation, {
202
+ fallback: /* @__PURE__ */ jsx(EditingFallbackSlot, { layoutPage: !!input.derived }),
203
+ children: editor
204
+ })
219
205
  });
220
- $[22] = editor;
221
- $[23] = input.derived;
222
- $[24] = input.previewDocument;
223
- $[25] = input.routeKind;
224
- $[26] = readOnlyPage;
225
- $[27] = t9;
226
- } else t9 = $[27];
206
+ $[20] = editor;
207
+ $[21] = input.derived;
208
+ $[22] = input.routeKind;
209
+ $[23] = t9;
210
+ } else t9 = $[23];
227
211
  let t10;
228
- if ($[28] !== fallback || $[29] !== t9) {
229
- t10 = /* @__PURE__ */ jsx(EditingActivationBoundary, {
230
- fallback,
231
- children: t9
232
- });
233
- $[28] = fallback;
234
- $[29] = t9;
235
- $[30] = t10;
236
- } else t10 = $[30];
237
- let t11;
238
- if ($[31] !== input || $[32] !== t10 || $[33] !== t8) {
239
- t11 = /* @__PURE__ */ jsx(RuntimeChromeSlot, {
212
+ if ($[24] !== input || $[25] !== t7 || $[26] !== t9) {
213
+ t10 = /* @__PURE__ */ jsx(RuntimeChromeSlot, {
240
214
  input,
241
- fallback: t8,
242
- children: t10
215
+ fallback: t7,
216
+ children: t9
243
217
  });
244
- $[31] = input;
245
- $[32] = t10;
246
- $[33] = t8;
247
- $[34] = t11;
248
- } else t11 = $[34];
249
- return t11;
218
+ $[24] = input;
219
+ $[25] = t7;
220
+ $[26] = t9;
221
+ $[27] = t10;
222
+ } else t10 = $[27];
223
+ return t10;
250
224
  }
251
225
  if (studioOnly) return null;
252
226
  if (studioDocument) {
227
+ let t6;
228
+ if ($[28] === Symbol.for("react.memo_cache_sentinel")) {
229
+ t6 = { height: "100vh" };
230
+ $[28] = t6;
231
+ } else t6 = $[28];
253
232
  let t7;
254
- if ($[35] === Symbol.for("react.memo_cache_sentinel")) {
255
- t7 = { height: "100vh" };
256
- $[35] = t7;
257
- } else t7 = $[35];
258
- let t8;
259
- if ($[36] !== isolatedPage) {
260
- t8 = /* @__PURE__ */ jsx("div", {
261
- style: t7,
233
+ if ($[29] !== isolatedPage) {
234
+ t7 = /* @__PURE__ */ jsx("div", {
235
+ style: t6,
262
236
  children: isolatedPage
263
237
  });
264
- $[36] = isolatedPage;
265
- $[37] = t8;
266
- } else t8 = $[37];
267
- return t8;
238
+ $[29] = isolatedPage;
239
+ $[30] = t7;
240
+ } else t7 = $[30];
241
+ return t7;
268
242
  }
269
243
  return readOnlyPage;
270
244
  }
@@ -18,7 +18,7 @@ async function hydratePageWithApp(camoxApp) {
18
18
  ...hydrationData,
19
19
  dehydratedState: restoreArtifactSources(hydrationData.dehydratedState, root)
20
20
  };
21
- await Promise.all([camoxApp.loadBlocks(getPageBlockTypes(input.dehydratedState)).catch((error) => console.error("Camox: a block of this page failed to load.", error)), input.presentation === "studio" ? loadRuntimeChrome() : void 0]);
21
+ await (input.presentation === "studio" ? loadRuntimeChrome() : camoxApp.loadBlocks(getPageBlockTypes(input.dehydratedState)).catch((error) => console.error("Camox: a block of this page failed to load.", error)));
22
22
  const draftScope = createDraftScope({
23
23
  queryClient,
24
24
  browser: window
@@ -93,12 +93,13 @@ function PageNavigationProvider({ camoxApp, children, initialInput, queryClient
93
93
  if (!response.ok) throw new Error(`Navigation failed: ${response.status}`);
94
94
  const next = await response.json();
95
95
  if (next.pathname !== pathname || !next.dehydratedState) throw new Error("Invalid navigation response");
96
- await camoxApp.loadBlocks(getPageBlockTypes(next.dehydratedState));
96
+ if (initialInput.presentation !== "studio") await camoxApp.loadBlocks(getPageBlockTypes(next.dehydratedState));
97
97
  return next;
98
98
  }, [
99
99
  camoxApp,
100
100
  draftScope,
101
- initialInput.runtimeBasePath
101
+ initialInput.runtimeBasePath,
102
+ initialInput.presentation
102
103
  ]);
103
104
  const [prefetched] = React.useState(() => createPrefetchCache());
104
105
  const headManager = React.useRef(null);
@@ -1,6 +1,8 @@
1
1
  import { prefersMarkdown } from "../routes/pageRuntime.js";
2
2
 
3
3
  //#region src/features/runtime/publicCache.ts
4
+ /** The named cache public renders are kept in. */
5
+ const PUBLIC_CACHE_NAME = "camox-public-renders";
4
6
  /** How long the edge keeps a render. Live changing replaces its key long before. */
5
7
  const EDGE_MAX_AGE = 86400;
6
8
  /** Browsers can't be purged, so they ask again each time; the edge answers them. */
@@ -24,11 +26,21 @@ async function respondThroughPublicCache(request, publicCache, render) {
24
26
  const liveCacheKey = await publicCache.liveCacheKey();
25
27
  if (!liveCacheKey) return render();
26
28
  const key = cacheKeyOf(request, `${liveCacheKey}.${publicCache.build}`);
27
- const cached = await publicCache.cache.match(withValidators(key, request));
29
+ let cache;
30
+ let cached;
31
+ try {
32
+ cache = await publicCache.cache();
33
+ cached = await cache.match(withValidators(key, request));
34
+ } catch (error) {
35
+ console.error("The edge cache couldn't be read: the page renders without it", error);
36
+ return render();
37
+ }
28
38
  if (cached) return forVisitors(cached, request);
29
39
  const response = await render();
30
40
  if (!response || !mayBeShared(response)) return response;
31
- publicCache.waitUntil(publicCache.cache.put(key, forEdge(response.clone())));
41
+ publicCache.waitUntil((async () => cache.put(key, forEdge(response.clone())))().catch((error) => {
42
+ console.error("The edge cache couldn't keep a render", error);
43
+ }));
32
44
  return forVisitors(response, request);
33
45
  }
34
46
  /**
@@ -80,4 +92,4 @@ function forVisitors(response, request) {
80
92
  }
81
93
 
82
94
  //#endregion
83
- export { readLiveCacheKey, respondThroughPublicCache };
95
+ export { PUBLIC_CACHE_NAME, readLiveCacheKey, respondThroughPublicCache };
@@ -0,0 +1,23 @@
1
+ import { PUBLISHING_DRAFT_VAR } from "@camox/api-contract/drafts";
2
+
3
+ //#region src/features/runtime/publishingDraft.ts
4
+ /**
5
+ * The Worker a publish with code uploaded carries the publishing draft's ID as a variable
6
+ * (`PUBLISHING_DRAFT_VAR`). Its reads of the API name that draft, so while the draft publishes
7
+ * they see Live overlaid with it, the content its code was previewed with. Once the draft has
8
+ * ended, the API reads Live for them. The variable is the same for every request a Worker
9
+ * serves, so it's kept from the first.
10
+ */
11
+ const PUBLISHING_DRAFT_GLOBAL = "__CAMOX_PUBLISHING_DRAFT__";
12
+ /** Keeps the publishing draft a Worker's environment names, if it names one. */
13
+ function rememberPublishingDraft(env) {
14
+ const draft = env?.[PUBLISHING_DRAFT_VAR];
15
+ if (typeof draft === "string" && draft) globalThis[PUBLISHING_DRAFT_GLOBAL] = draft;
16
+ }
17
+ /** The draft this Worker publishes, if a publish with code uploaded it. */
18
+ function publishingDraft() {
19
+ return globalThis[PUBLISHING_DRAFT_GLOBAL];
20
+ }
21
+
22
+ //#endregion
23
+ export { publishingDraft, rememberPublishingDraft };
@@ -0,0 +1,8 @@
1
+ import { CamoxApp } from "../../core/createApp.js";
2
+ //#region src/features/runtime/rendererClient.d.ts
3
+ /**
4
+ * Starts the renderer frame's document (ADR 0023): connects to the studio that loaded it, and
5
+ * renders the pages of the frames it is given with the site's code, in a cache of its own.
6
+ */
7
+ export declare function startRendererWithApp(camoxApp: CamoxApp): Promise<void>;
8
+ //#endregion
@@ -0,0 +1,77 @@
1
+ import { getDraftReadClient } from "../../lib/api-client.js";
2
+ import { DraftScopeContext, createDraftScope, followUrl } from "../../lib/draft-scope.js";
3
+ import { StudioCacheContext, createStudioCache } from "../../lib/studio-reads.js";
4
+ import { STUDIO_HANDLE_KEY } from "../renderer/protocol.js";
5
+ import { CoreCamoxProvider } from "../provider/CoreCamoxProvider.js";
6
+ import { readHydrationData } from "./hydrationData.js";
7
+ import { CompleteBlockEditingRuntimeProvider } from "../../core/editing/CompleteBlockEditingRuntime.js";
8
+ import { RendererApp } from "../renderer/RendererApp.js";
9
+ import { connectToStudio } from "../renderer/studioConnection.js";
10
+ import { jsx } from "react/jsx-runtime";
11
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
12
+ import { createRoot } from "react-dom/client";
13
+
14
+ //#region src/features/runtime/rendererClient.tsx
15
+ /**
16
+ * Starts the renderer frame's document (ADR 0023): connects to the studio that loaded it, and
17
+ * renders the pages of the frames it is given with the site's code, in a cache of its own.
18
+ */
19
+ async function startRendererWithApp(camoxApp) {
20
+ const handle = window.frameElement?.[STUDIO_HANDLE_KEY];
21
+ if (!handle) {
22
+ console.error("Camox: the renderer document only runs in the studio's renderer frame.");
23
+ return;
24
+ }
25
+ const input = readHydrationData();
26
+ if (!input.apiUrl || !input.authenticationUrl || !input.projectUuid) {
27
+ console.error("Camox: the renderer document is missing its API or project.");
28
+ return;
29
+ }
30
+ const codeLoaded = camoxApp.loadAllBlocks();
31
+ codeLoaded.catch((error) => console.error("Camox: the site's code failed to load.", error));
32
+ const queryClient = new QueryClient();
33
+ const draftScope = createDraftScope({
34
+ queryClient,
35
+ browser: window
36
+ });
37
+ followUrl(draftScope, window.location.href);
38
+ const studioCache = createStudioCache({
39
+ queryClient,
40
+ scope: draftScope,
41
+ api: getDraftReadClient
42
+ });
43
+ const connection = connectToStudio(handle, {
44
+ queryClient,
45
+ studioCache,
46
+ draftScope,
47
+ camoxApp
48
+ });
49
+ const container = document.createElement("div");
50
+ document.body.append(container);
51
+ createRoot(container).render(/* @__PURE__ */ jsx(QueryClientProvider, {
52
+ client: queryClient,
53
+ children: /* @__PURE__ */ jsx(DraftScopeContext.Provider, {
54
+ value: draftScope,
55
+ children: /* @__PURE__ */ jsx(StudioCacheContext.Provider, {
56
+ value: studioCache,
57
+ children: /* @__PURE__ */ jsx(CoreCamoxProvider, {
58
+ apiUrl: input.apiUrl,
59
+ authenticationUrl: input.authenticationUrl,
60
+ camoxApp,
61
+ projectUuid: input.projectUuid,
62
+ initialAuthenticated: true,
63
+ children: /* @__PURE__ */ jsx(CompleteBlockEditingRuntimeProvider, { children: /* @__PURE__ */ jsx(RendererApp, {
64
+ connection,
65
+ camoxApp,
66
+ codeLoaded,
67
+ projectUuid: input.projectUuid,
68
+ runtimeBasePath: input.runtimeBasePath
69
+ }) })
70
+ })
71
+ })
72
+ })
73
+ }));
74
+ }
75
+
76
+ //#endregion
77
+ export { startRendererWithApp };
@@ -89,6 +89,8 @@ export interface RuntimeOptions {
89
89
  developmentDraft?: boolean;
90
90
  pageClientEntryUrl?: string;
91
91
  studioClientEntryUrl?: string;
92
+ /** The renderer frame's client entry, which renders the studio's pages (ADR 0023). */
93
+ rendererClientEntryUrl?: string;
92
94
  getCamoxApp?: () => Promise<CamoxApp>;
93
95
  getDocument?: () => Promise<CamoxDocument>;
94
96
  projectUuid?: string;
@@ -107,6 +109,13 @@ export declare function getRuntimePathname(url: string, runtimeBasePath?: string
107
109
  export declare function isPublicRequest(request: Request, options?: RuntimeOptions): boolean;
108
110
  export declare function matchRuntimeRoute(pathname: string): RuntimeRouteMatch;
109
111
  export declare function createPageHeadInput(head: PageHead): UseHeadInput;
112
+ /** What the renderer document tells its client: where the API, sign-in and runtime are. */
113
+ export interface RendererDocumentInput {
114
+ apiUrl?: string;
115
+ authenticationUrl?: string;
116
+ projectUuid?: string;
117
+ runtimeBasePath: string;
118
+ }
110
119
  export declare function handleCamoxRequest(request: Request, options?: RuntimeOptions): Promise<Response | null>;
111
120
  //#endregion
112
121
  export {};
@@ -6,13 +6,14 @@ import { draftNamedBy } from "../../lib/draft-scope.js";
6
6
  import { RUNTIME_ROUTE_PATHS, matchRuntimeOwnedRoute } from "./runtimeRoutes.js";
7
7
  import { reads } from "../../lib/studio-reads.js";
8
8
  import { asCollectionItem, recordMarkdownTemplate, recordPageValues } from "../../core/recordItem.js";
9
- import { buildLayoutContext, curatedLayoutContext } from "../../core/layoutContext.js";
10
9
  import { PLATFORM_SCRIPT } from "../../lib/platform.js";
11
10
  import { STUDIO_ASSETS_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, STUDIO_SETTINGS_PATH, matchCollectionContentPath } from "../studio/routes.js";
12
11
  import { STUDIO_THEME_SCRIPT } from "../studio/studioTheme.js";
13
12
  import { DefaultPageImage, renderPageImage } from "../og/pageImage.js";
14
13
  import { discoverLayoutPages, matchDerivedLayout } from "../../core/derivedRoutes.js";
14
+ import { buildLayoutContext, curatedLayoutContext } from "../../core/layoutContext.js";
15
15
  import { buildCamoxPageHead, buildFaviconUrl, buildPageHead, buildPageMeta, createServerApiClient, createServerStudioCache, curatedMarkdownResponse, declarePage, isNotFoundError, loadCamoxPageForRequest, parseMarkdownPath, prefersMarkdown, readDraftOrLive, readPageMarkdown, readScopeLocales, renderedHref, resolveMemberProject, withFrontMatter } from "../routes/pageRuntime.js";
16
+ import { assetPrefix } from "./assetPrefix.js";
16
17
  import { alternatesByPage, findAlternates, hreflangLinks } from "./alternates.js";
17
18
  import { configuredProviders, renderAnalyticsScripts } from "./analytics.js";
18
19
  import { omitArtifactSources } from "./artifactSources.js";
@@ -459,8 +460,9 @@ function renderHead(document, pageHead, stylesheetUrl, studioStylesheetUrl, loca
459
460
  return renderSSRHead(head);
460
461
  }
461
462
  /**
462
- * The browser loads a page's blocks before hydrating it. Preloading them alongside the entry
463
- * script avoids a request waterfall.
463
+ * A visitor's browser loads a page's blocks before hydrating it. Preloading them alongside the
464
+ * entry script avoids a request waterfall. A member's document loads none: only its renderer
465
+ * frame runs the site's code.
464
466
  */
465
467
  function renderBlockPreloads(options, dehydratedState) {
466
468
  const preloads = options.blockPreloads;
@@ -470,24 +472,40 @@ function renderBlockPreloads(options, dehydratedState) {
470
472
  const modulePath = preloads.modulePaths[type];
471
473
  if (!modulePath) continue;
472
474
  if (!preloads.chunks) urls.add(modulePath);
473
- for (const chunk of preloads.chunks?.[modulePath] ?? []) urls.add(`/${chunk}`);
475
+ for (const chunk of preloads.chunks?.[modulePath] ?? []) urls.add(`${assetPrefix()}${chunk}`);
474
476
  }
475
477
  return [...urls].map((url) => `<link rel="modulepreload" href="${escapeXml(url)}">`).join("");
476
478
  }
479
+ /**
480
+ * The renderer frame's document (ADR 0023): an empty document running the renderer's client,
481
+ * which renders the studio's pages into its frames, reading them itself. It holds no content, but
482
+ * is only ever the studio's, so no shared cache keeps it.
483
+ */
484
+ function createRendererDocumentResponse(options) {
485
+ const input = {
486
+ apiUrl: options.apiUrl,
487
+ authenticationUrl: options.authenticationUrl,
488
+ projectUuid: options.projectUuid,
489
+ runtimeBasePath: normalizeRuntimeBasePath(options.runtimeBasePath)
490
+ };
491
+ return new Response(`<!doctype html><html><head><meta charset="utf-8"><meta name="robots" content="noindex"><title>Camox renderer</title><script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(input)}<\/script></head><body>${renderClientEntryScript(options.rendererClientEntryUrl)}</body></html>`, { headers: {
492
+ "Content-Type": "text/html; charset=utf-8",
493
+ "Cache-Control": "private, no-store",
494
+ "X-Robots-Tag": "noindex"
495
+ } });
496
+ }
477
497
  function renderClientEntryScript(src) {
478
498
  if (!src) return "";
479
499
  return `<script type="module" src="${escapeXml(src)}"><\/script>`;
480
500
  }
481
- async function preparePreviewDocument(input, options, document, content) {
501
+ /**
502
+ * The site document the studio's frames load: the site's head and an empty root, which the
503
+ * renderer frame renders the page into. The studio never renders the site's code itself.
504
+ */
505
+ function preparePreviewDocument(input, options, document) {
482
506
  const siteHead = renderHead(document, createPageHeadInput(input.head ?? {}), options.stylesheetUrl, void 0, input.locale);
483
507
  const href = new URL(withRuntimeBasePath(input.pathname, options.runtimeBasePath), input.href).href;
484
- const siteHtml = content ?? (options.renderPage ? await options.renderPage({
485
- ...input,
486
- href,
487
- presentation: "public",
488
- previewDocument: void 0
489
- }) : "");
490
- input.previewDocument = `<!doctype html><html${siteHead.htmlAttrs}><head><base href="${escapeXml(href)}">${siteHead.headTags}</head><body${siteHead.bodyAttrs}>${siteHead.bodyTagsOpen}<div id="root" data-camox-preview-root>${siteHtml}</div>${siteHead.bodyTags}</body></html>`;
508
+ input.previewDocument = `<!doctype html><html${siteHead.htmlAttrs}><head><base href="${escapeXml(href)}">${siteHead.headTags}</head><body${siteHead.bodyAttrs}>${siteHead.bodyTagsOpen}<div id="root" data-camox-preview-root></div>${siteHead.bodyTags}</body></html>`;
491
509
  }
492
510
  /**
493
511
  * The render input a page's HTML embeds for hydration. Visitors load public pages, whose HTML
@@ -832,7 +850,7 @@ async function createPageHtmlResponse({ options, pathname, located, request, pag
832
850
  locale: located.locale,
833
851
  locales: located.locales
834
852
  };
835
- await preparePreviewDocument(pageRenderInput, options, document, studio ? void 0 : "");
853
+ preparePreviewDocument(pageRenderInput, options, document);
836
854
  const appHtml = await options.renderPage(pageRenderInput);
837
855
  const renderedHead = renderHead(studio ? {} : document, createPageHeadInput(head), studio ? void 0 : options.stylesheetUrl, studio ? options.studioStylesheetUrl : void 0, studio ? void 0 : located.locale);
838
856
  return new Response(`<!doctype html>
@@ -841,7 +859,7 @@ async function createPageHtmlResponse({ options, pathname, located, request, pag
841
859
  ${renderStudioThemeScript(studio)}
842
860
  ${renderedHead.headTags}
843
861
  ${renderAnalyticsScripts(analytics)}
844
- ${renderBlockPreloads(options, dehydratedState)}
862
+ ${studio ? "" : renderBlockPreloads(options, dehydratedState)}
845
863
  <script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(hydrationInputFor(pageRenderInput, appHtml))}<\/script>
846
864
  </head>
847
865
  <body${renderedHead.bodyAttrs}>
@@ -1116,7 +1134,7 @@ async function createDerivedResponse(options, pathname, located, request, { data
1116
1134
  }
1117
1135
  };
1118
1136
  const document = await options.getDocument?.() ?? {};
1119
- await preparePreviewDocument(input, options, document, !studio && !dataOnly ? "" : void 0);
1137
+ preparePreviewDocument(input, options, document);
1120
1138
  if (dataOnly) return Response.json(input, { headers: {
1121
1139
  ...isPrivateRender(request, options) ? { "Cache-Control": "private, no-store" } : {},
1122
1140
  Vary: "Cookie",
@@ -1124,7 +1142,7 @@ async function createDerivedResponse(options, pathname, located, request, { data
1124
1142
  } });
1125
1143
  const html = await options.renderPage(input);
1126
1144
  const head = renderHead(studio ? {} : document, createPageHeadInput(pageHead), studio ? void 0 : options.stylesheetUrl, studio ? options.studioStylesheetUrl : void 0, studio ? void 0 : located.locale);
1127
- return new Response(`<!doctype html><html${head.htmlAttrs}><head>${renderStudioThemeScript(studio)}${head.headTags}${renderAnalyticsScripts(analytics)}${renderBlockPreloads(options, input.dehydratedState)}<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(hydrationInputFor(input, html))}<\/script></head><body${head.bodyAttrs}>${head.bodyTagsOpen}<div id="root">${html}</div>${head.bodyTags}${renderClientEntryScript(options.pageClientEntryUrl)}</body></html>`, { headers: {
1145
+ return new Response(`<!doctype html><html${head.htmlAttrs}><head>${renderStudioThemeScript(studio)}${head.headTags}${renderAnalyticsScripts(analytics)}${studio ? "" : renderBlockPreloads(options, input.dehydratedState)}<script id="__CAMOX_DATA__" type="application/json">${serializeJsonForHtml(hydrationInputFor(input, html))}<\/script></head><body${head.bodyAttrs}>${head.bodyTagsOpen}<div id="root">${html}</div>${head.bodyTags}${renderClientEntryScript(options.pageClientEntryUrl)}</body></html>`, { headers: {
1128
1146
  "Content-Type": "text/html; charset=utf-8",
1129
1147
  Vary: "Cookie",
1130
1148
  Link: llmsTxtLinkHeader(options),
@@ -1284,7 +1302,7 @@ async function createPageDataResponse({ options, pathname, located, request, pag
1284
1302
  head,
1285
1303
  dehydratedState
1286
1304
  };
1287
- await preparePreviewDocument(input, options, await options.getDocument?.() ?? {});
1305
+ preparePreviewDocument(input, options, await options.getDocument?.() ?? {});
1288
1306
  return Response.json(input, { headers });
1289
1307
  } catch (error) {
1290
1308
  if (isNotFoundError(error)) {
@@ -1313,6 +1331,7 @@ async function handleCamoxRequest(request, options = {}) {
1313
1331
  });
1314
1332
  }
1315
1333
  if (match.kind === "og") return createOgResponse(request, options);
1334
+ if (match.kind === "renderer") return createRendererDocumentResponse(options);
1316
1335
  if (match.kind === "logo") return createProjectLogoResponse(request, options);
1317
1336
  if (match.kind === "sitemap") return createSitemapResponse(request, options);
1318
1337
  if (match.kind === "llms-txt") return createLlmsTxtResponse(request, options);
@@ -10,6 +10,8 @@ export declare const RUNTIME_ROUTE_PATHS: {
10
10
  readonly data: "/_camox/data";
11
11
  readonly health: "/_camox/health";
12
12
  readonly registry: "/_camox/registry";
13
+ /** The renderer frame's document (ADR 0023), which the studio loads to render its pages. */
14
+ readonly renderer: "/_camox/renderer";
13
15
  readonly routes: "/_camox/routes";
14
16
  readonly logo: "/_camox/logo.png";
15
17
  readonly og: "/og";
@@ -10,6 +10,8 @@ const RUNTIME_ROUTE_PATHS = {
10
10
  data: "/_camox/data",
11
11
  health: "/_camox/health",
12
12
  registry: "/_camox/registry",
13
+ /** The renderer frame's document (ADR 0023), which the studio loads to render its pages. */
14
+ renderer: "/_camox/renderer",
13
15
  routes: "/_camox/routes",
14
16
  logo: "/_camox/logo.png",
15
17
  og: "/og",
@@ -1,10 +1,19 @@
1
+ import { WorkerEntrypoint } from "cloudflare:workers";
1
2
  //#region src/features/runtime/worker.d.ts
2
3
  /** What the Worker gets with each request: only `waitUntil` matters here. */
3
4
  interface WorkerContext {
4
5
  waitUntil(promise: Promise<unknown>): void;
5
6
  }
7
+ /**
8
+ * The website's Definition Set, for the Worker that loads a revision, which calls it by the name
9
+ * `DEFINITION_SET_ENTRYPOINT` holds. A named `WorkerEntrypoint` takes RPC calls only: no HTTP
10
+ * request reaches it.
11
+ */
12
+ export declare class CamoxDefinitionSet extends WorkerEntrypoint {
13
+ read(): Promise<unknown>;
14
+ }
6
15
  declare const _default: {
7
- fetch(request: Request, _env?: unknown, context?: WorkerContext): Promise<Response>;
16
+ fetch(request: Request, env?: unknown, context?: WorkerContext): Promise<Response>;
8
17
  };
9
18
  //#endregion
10
19
  export { _default as default };
@@ -1,7 +1,9 @@
1
+ import { rememberPublishingDraft } from "./publishingDraft.js";
1
2
  import { fetchLocalhostOnLoopback } from "./loopbackFetch.js";
2
- import { readLiveCacheKey, respondThroughPublicCache } from "./publicCache.js";
3
+ import { PUBLIC_CACHE_NAME, readLiveCacheKey, respondThroughPublicCache } from "./publicCache.js";
3
4
  import { shouldHandleRequest, withDevHtml } from "./workerRequest.js";
4
- import { devHtml, handleCamoxRequest, isPublicRequest, publicCacheSource, runtimeBasePath } from "virtual:camox/server";
5
+ import { WorkerEntrypoint } from "cloudflare:workers";
6
+ import { devHtml, handleCamoxRequest, isPublicRequest, publicCacheSource, readDefinitionSet, runtimeBasePath } from "virtual:camox/server";
5
7
 
6
8
  //#region src/features/runtime/worker.ts
7
9
  /**
@@ -19,17 +21,28 @@ function notFound() {
19
21
  * its context can't keep anything past the response.
20
22
  */
21
23
  function respond(request, context) {
22
- const cache = globalThis.caches?.default;
23
- if (devHtml || !context || !cache || !isPublicRequest(request)) return handleCamoxRequest(request);
24
+ const caches = globalThis.caches;
25
+ if (devHtml || !context || !caches || !isPublicRequest(request)) return handleCamoxRequest(request);
24
26
  const { apiUrl, projectUuid, build } = publicCacheSource;
25
27
  return respondThroughPublicCache(request, {
26
- cache,
28
+ cache: () => caches.open(PUBLIC_CACHE_NAME),
27
29
  liveCacheKey: () => readLiveCacheKey(apiUrl, projectUuid),
28
30
  build,
29
31
  waitUntil: (promise) => context.waitUntil(promise)
30
32
  }, () => handleCamoxRequest(request));
31
33
  }
32
- var worker_default = { async fetch(request, _env, context) {
34
+ /**
35
+ * The website's Definition Set, for the Worker that loads a revision, which calls it by the name
36
+ * `DEFINITION_SET_ENTRYPOINT` holds. A named `WorkerEntrypoint` takes RPC calls only: no HTTP
37
+ * request reaches it.
38
+ */
39
+ var CamoxDefinitionSet = class extends WorkerEntrypoint {
40
+ read() {
41
+ return readDefinitionSet();
42
+ }
43
+ };
44
+ var worker_default = { async fetch(request, env, context) {
45
+ rememberPublishingDraft(env);
33
46
  if (!shouldHandleRequest(request, runtimeBasePath)) return notFound();
34
47
  const response = await respond(request, context);
35
48
  if (!response) return notFound();
@@ -37,4 +50,4 @@ var worker_default = { async fetch(request, _env, context) {
37
50
  } };
38
51
 
39
52
  //#endregion
40
- export { worker_default as default };
53
+ export { CamoxDefinitionSet, worker_default as default };