camox 0.48.0 → 0.52.1

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 (127) 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/layoutContext.d.ts +1 -0
  6. package/dist/core/lib/contentType.js +2 -2
  7. package/dist/core/recordItem.js +3 -3
  8. package/dist/features/canvas/CamoxCanvas.js +15 -15
  9. package/dist/features/canvas/CanvasLoadingScreen.js +15 -11
  10. package/dist/features/canvas/CanvasPageFrame.js +81 -87
  11. package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -1
  12. package/dist/features/canvas/DerivedPagePathInput.js +12 -12
  13. package/dist/features/canvas/canvasOverlayGeometry.js +1 -1
  14. package/dist/features/canvas/canvasStore.js +2 -2
  15. package/dist/features/canvas/sidebar/CanvasInspector.js +1 -1
  16. package/dist/features/canvas/sidebar/CuratedPageInspector.js +1 -1
  17. package/dist/features/canvas/sidebar/LayoutPageBlocks.js +23 -31
  18. package/dist/features/canvas/useCanvasCamera.js +1 -1
  19. package/dist/features/canvas/useDiscoveredPreviewPaths.js +16 -9
  20. package/dist/features/content/ContentAssets.js +84 -88
  21. package/dist/features/page/CuratedPageContent.js +1 -1
  22. package/dist/features/preview/CamoxPreview.d.ts +8 -4
  23. package/dist/features/preview/CamoxPreview.js +258 -443
  24. package/dist/features/preview/EditablePageContent.js +244 -2
  25. package/dist/features/preview/EditablePageExperience.js +19 -13
  26. package/dist/features/preview/components/AddBlockSheet.js +55 -15
  27. package/dist/features/preview/components/BlockActionsPopover.js +23 -51
  28. package/dist/features/preview/components/BlockPeek.js +66 -0
  29. package/dist/features/preview/components/BlockThumbnail.js +134 -119
  30. package/dist/features/preview/components/CanvasPreview.js +1 -1
  31. package/dist/features/preview/components/CreatePageModal.js +150 -156
  32. package/dist/features/preview/components/FieldToolbar.js +1 -1
  33. package/dist/features/preview/components/Frame.js +27 -4
  34. package/dist/features/preview/components/MobilePreviewDrawer.js +6 -9
  35. package/dist/features/preview/components/PageInfo.js +161 -126
  36. package/dist/features/preview/components/PagePicker.js +2 -2
  37. package/dist/features/preview/components/PageTree.js +174 -190
  38. package/dist/features/preview/components/PreviewPanel.js +13 -67
  39. package/dist/features/preview/components/PreviewToolbar.js +1 -1
  40. package/dist/features/preview/components/ReferenceFieldEditor.js +4 -4
  41. package/dist/features/preview/components/ReferenceListFieldEditor.js +4 -4
  42. package/dist/features/preview/components/RemovedBlockPlaceholder.js +37 -70
  43. package/dist/features/preview/components/RepeatableItemsList.js +1 -1
  44. package/dist/features/preview/components/SelectionEditor.js +248 -256
  45. package/dist/features/preview/components/StudioEditingFallback.js +22 -26
  46. package/dist/features/preview/components/useFieldWriter.js +19 -19
  47. package/dist/features/preview/components/useRecordView.js +105 -147
  48. package/dist/features/preview/components/useRepeatableItemActions.js +0 -2
  49. package/dist/features/preview/pageRecord.js +30 -24
  50. package/dist/features/preview/previewConstants.js +2 -8
  51. package/dist/features/preview/previewStore.js +2 -0
  52. package/dist/features/preview/studioNotice.js +20 -0
  53. package/dist/features/preview/usePreviewViewportActions.js +1 -1
  54. package/dist/features/provider/AuthenticatedCamoxProvider.js +47 -31
  55. package/dist/features/provider/components/CommandPalette.js +2 -5
  56. package/dist/features/provider/signInShortcut.js +1 -2
  57. package/dist/features/provider/useAdminShortcuts.js +47 -53
  58. package/dist/features/renderer/RendererApp.js +542 -0
  59. package/dist/features/renderer/RendererFrames.js +450 -0
  60. package/dist/features/renderer/definitionAnswers.js +26 -0
  61. package/dist/features/renderer/protocol.js +35 -0
  62. package/dist/features/renderer/rendererHost.js +249 -0
  63. package/dist/features/renderer/studioConnection.js +186 -0
  64. package/dist/features/routes/pageRuntime.d.ts +2 -1
  65. package/dist/features/routes/pageRuntime.js +34 -22
  66. package/dist/features/runtime/RuntimeChrome.js +2 -5
  67. package/dist/features/runtime/assetPrefix.js +14 -0
  68. package/dist/features/runtime/breadcrumbs.js +1 -1
  69. package/dist/features/runtime/editingFallback.js +4 -8
  70. package/dist/features/runtime/livePages.js +1 -1
  71. package/dist/features/runtime/pageApp.js +57 -83
  72. package/dist/features/runtime/pageClient.js +1 -1
  73. package/dist/features/runtime/pageNavigation.js +3 -2
  74. package/dist/features/runtime/publicCache.js +95 -0
  75. package/dist/features/runtime/publishingDraft.js +23 -0
  76. package/dist/features/runtime/rendererClient.d.ts +8 -0
  77. package/dist/features/runtime/rendererClient.js +77 -0
  78. package/dist/features/runtime/runtime.d.ts +22 -3
  79. package/dist/features/runtime/runtime.js +209 -96
  80. package/dist/features/runtime/runtimeRoutes.d.ts +2 -0
  81. package/dist/features/runtime/runtimeRoutes.js +2 -0
  82. package/dist/features/runtime/worker.d.ts +14 -1
  83. package/dist/features/runtime/worker.js +34 -4
  84. package/dist/features/settings/AnalyticsSettings.js +622 -0
  85. package/dist/features/settings/CamoxSettings.js +95 -0
  86. package/dist/features/settings/FaviconSettings.js +254 -0
  87. package/dist/features/settings/ProjectNameSettings.js +145 -0
  88. package/dist/features/settings/SettingsPart.js +73 -0
  89. package/dist/features/settings/SocialProfilesSettings.js +245 -0
  90. package/dist/features/settings/useProjectMutation.js +61 -0
  91. package/dist/features/studio/FollowAgentProvider.js +10 -10
  92. package/dist/features/studio/components/DraftsControl.js +136 -131
  93. package/dist/features/studio/components/LocalePicker.js +178 -174
  94. package/dist/features/studio/components/Navbar.js +107 -66
  95. package/dist/features/studio/components/ProjectMenu.js +119 -171
  96. package/dist/features/studio/components/ReviewDraftSheet.js +1151 -991
  97. package/dist/features/studio/components/ThreadMessages.js +100 -65
  98. package/dist/features/studio/components/UserButton.js +2 -2
  99. package/dist/features/studio/routes.js +3 -1
  100. package/dist/features/studio/showInCanvas.js +129 -0
  101. package/dist/features/vite/definitionsSync.js +5 -6
  102. package/dist/features/vite/hostedChecks.js +127 -0
  103. package/dist/features/vite/runtimeDev.js +58 -17
  104. package/dist/features/vite/staticAssetHeaders.js +52 -0
  105. package/dist/features/vite/vite.d.ts +1 -1
  106. package/dist/features/vite/vite.js +49 -9
  107. package/dist/hooks/use-page-destinations.js +10 -10
  108. package/dist/lib/api-client-server.d.ts +1 -1
  109. package/dist/lib/api-client.d.ts +2 -1
  110. package/dist/lib/draft-scope.d.ts +1 -0
  111. package/dist/lib/locale-path.d.ts +3 -11
  112. package/dist/lib/locale-path.js +3 -27
  113. package/dist/lib/queries.js +7 -1
  114. package/dist/lib/revisions.js +20 -0
  115. package/dist/lib/shortcuts.js +29 -0
  116. package/dist/lib/studio-definitions.js +160 -0
  117. package/dist/lib/studio-reads.d.ts +1 -0
  118. package/dist/lib/studio-reads.js +65 -21
  119. package/dist/lib/use-project-room.js +16 -10
  120. package/dist/lib/utils.js +1 -104
  121. package/dist/package.js +1 -1
  122. package/dist/studio.css +1 -1
  123. package/package.json +9 -4
  124. package/skills/camox/SKILL.md +4 -0
  125. package/skills/camox/references/artifacts.md +1 -1
  126. package/dist/features/studio/components/FaviconDialog.js +0 -318
  127. package/dist/features/studio/components/SocialProfilesDialog.js +0 -342
@@ -0,0 +1,249 @@
1
+ import { referencePickerFocus } from "../preview/referencePickerFocus.js";
2
+ import { canvasStore } from "../canvas/canvasStore.js";
3
+ import { previewStore } from "../preview/previewStore.js";
4
+ import { SDK_VERSION, deferredInbox } from "./protocol.js";
5
+ import { createContentEditor } from "../../core/editing/contentEdits.js";
6
+ import { LIVE_KEY_SCOPE, scopeOfKey } from "@camox/api-contract/query-keys";
7
+
8
+ //#region src/features/renderer/rendererHost.ts
9
+ const mirrored = {
10
+ preview: previewStore,
11
+ canvas: canvasStore
12
+ };
13
+ const forwarded = {
14
+ preview: previewStore,
15
+ referencePickerFocus
16
+ };
17
+ /**
18
+ * The host of the studio whose cache is `studioCache`, viewing the draft `draftScope` names, which
19
+ * the renderer shows too. Without them, the renderer hears nothing of the studio's cache.
20
+ * The edits the renderer makes in place are written with the studio's own mutations, through
21
+ * `queryClient`; without it, every one fails. What the renderer's pages tell the editor goes to
22
+ * `notify`.
23
+ */
24
+ function createRendererHost({ studioCache, draftScope, queryClient, notify } = {}) {
25
+ const applyEdit = queryClient && studioCache ? createContentEditor({
26
+ queryClient,
27
+ studioCache
28
+ }) : void 0;
29
+ const frames = /* @__PURE__ */ new Map();
30
+ const thumbnails = /* @__PURE__ */ new Map();
31
+ /** The questions asked that no renderer answered yet. */
32
+ const asked = /* @__PURE__ */ new Map();
33
+ let lastAskId = 0;
34
+ const connectionListeners = /* @__PURE__ */ new Set();
35
+ let status = "connecting";
36
+ const statusListeners = /* @__PURE__ */ new Set();
37
+ const setStatus = (next) => {
38
+ if (status === next) return;
39
+ status = next;
40
+ for (const listener of statusListeners) listener();
41
+ };
42
+ let renderer = null;
43
+ /** Sends to the renderer once it said hello, in the protocol it speaks. */
44
+ let welcomed = false;
45
+ const post = (message) => {
46
+ if (welcomed) renderer?.(message);
47
+ };
48
+ const postStore = (store) => post({
49
+ type: "store",
50
+ store,
51
+ context: mirrored[store].getSnapshot().context
52
+ });
53
+ const subscriptions = Object.keys(mirrored).map((store) => mirrored[store].subscribe(() => postStore(store)));
54
+ const shownScope = () => draftScope?.current() ?? LIVE_KEY_SCOPE;
55
+ const shown = (queryKey) => scopeOfKey(queryKey) === shownScope();
56
+ /** Whether a project room invalidation reaches the reads of the draft the renderer shows. */
57
+ const reaches = async (message, projectId) => {
58
+ if (message.draftId === null) return true;
59
+ if (!draftScope) return false;
60
+ return await draftScope.keyScopeOfDraft(message.draftId, projectId) === shownScope();
61
+ };
62
+ const relayChange = (change) => {
63
+ switch (change.type) {
64
+ case "received": {
65
+ const { message, projectId } = change;
66
+ reaches(message, projectId).then((reached) => reached && post({
67
+ type: "invalidate",
68
+ message,
69
+ projectId
70
+ }));
71
+ return;
72
+ }
73
+ case "wrote":
74
+ if (!shown(change.queryKey)) return;
75
+ post({
76
+ type: "write",
77
+ queryKey: change.queryKey,
78
+ ..."value" in change && change.value !== void 0 && { value: change.value }
79
+ });
80
+ return;
81
+ case "outdated": {
82
+ const queryKeys = change.queryKeys.filter(shown);
83
+ if (queryKeys.length) post({
84
+ type: "outdated",
85
+ queryKeys
86
+ });
87
+ return;
88
+ }
89
+ }
90
+ };
91
+ const stopRelaying = studioCache?.subscribe(relayChange);
92
+ const stopFollowing = draftScope?.subscribe(({ draftId, move }) => {
93
+ if (move === "started" && draftId) post({
94
+ type: "draftStarted",
95
+ draftId
96
+ });
97
+ });
98
+ const receive = (message) => {
99
+ switch (message.type) {
100
+ case "hello":
101
+ if (message.protocol !== 1) return;
102
+ renderer?.({
103
+ type: "welcome",
104
+ protocol: 1,
105
+ sdkVersion: SDK_VERSION
106
+ });
107
+ if (message.sdkVersion !== SDK_VERSION) return setStatus("updating");
108
+ welcomed = true;
109
+ setStatus("connected");
110
+ for (const store of Object.keys(mirrored)) postStore(store);
111
+ for (const [frameId, { slot, target }] of frames) post({
112
+ type: "frame",
113
+ frameId,
114
+ slot,
115
+ target
116
+ });
117
+ for (const [thumbnailId, { slot, blockType }] of thumbnails) post({
118
+ type: "thumbnail",
119
+ thumbnailId,
120
+ slot,
121
+ blockType
122
+ });
123
+ for (const [askId, { question }] of asked) post({
124
+ type: "ask",
125
+ askId,
126
+ question
127
+ });
128
+ for (const listener of connectionListeners) listener();
129
+ return;
130
+ case "storeEvent": {
131
+ const store = forwarded[message.store];
132
+ if (!store) return;
133
+ store.send(message.event);
134
+ return;
135
+ }
136
+ case "rendered": return frames.get(message.frameId)?.hears.rendered?.();
137
+ case "error": return frames.get(message.frameId)?.hears.failed?.(message.message);
138
+ case "navigate": return frames.get(message.frameId)?.hears.navigate?.(message.to);
139
+ case "reveal": return frames.get(message.frameId)?.hears.reveal?.(message.element, message.immediate);
140
+ case "answer": {
141
+ const question = asked.get(message.askId);
142
+ if (!question) return;
143
+ asked.delete(message.askId);
144
+ if (message.error === void 0) question.resolve(message.value ?? null);
145
+ else question.reject(new Error(message.error));
146
+ return;
147
+ }
148
+ case "notify": return notify?.(message.notice);
149
+ case "edit": {
150
+ const { editId, edit } = message;
151
+ const sender = renderer;
152
+ const answer = (error) => {
153
+ if (renderer === sender) post({
154
+ type: "edited",
155
+ editId,
156
+ ...error && { error }
157
+ });
158
+ };
159
+ if (!applyEdit) return answer("The studio can't save changes made here.");
160
+ applyEdit(edit).then(() => answer(), (cause) => answer(cause instanceof Error ? cause.message : "Couldn't save the change."));
161
+ return;
162
+ }
163
+ }
164
+ };
165
+ return {
166
+ handle: { connect(inbox) {
167
+ renderer = deferredInbox(inbox);
168
+ welcomed = false;
169
+ const connected = renderer;
170
+ return deferredInbox((message) => {
171
+ if (renderer === connected) receive(message);
172
+ });
173
+ } },
174
+ show(frameId, slot, target, hears = {}) {
175
+ frames.set(frameId, {
176
+ slot,
177
+ target,
178
+ hears
179
+ });
180
+ post({
181
+ type: "frame",
182
+ frameId,
183
+ slot,
184
+ target
185
+ });
186
+ },
187
+ hide(frameId) {
188
+ if (!frames.delete(frameId)) return;
189
+ post({
190
+ type: "frameEnd",
191
+ frameId
192
+ });
193
+ },
194
+ ask(question) {
195
+ return new Promise((resolve, reject) => {
196
+ const askId = ++lastAskId;
197
+ asked.set(askId, {
198
+ question,
199
+ resolve,
200
+ reject
201
+ });
202
+ post({
203
+ type: "ask",
204
+ askId,
205
+ question
206
+ });
207
+ });
208
+ },
209
+ showThumbnail(thumbnailId, slot, blockType) {
210
+ thumbnails.set(thumbnailId, {
211
+ slot,
212
+ blockType
213
+ });
214
+ post({
215
+ type: "thumbnail",
216
+ thumbnailId,
217
+ slot,
218
+ blockType
219
+ });
220
+ },
221
+ hideThumbnail(thumbnailId) {
222
+ if (!thumbnails.delete(thumbnailId)) return;
223
+ post({
224
+ type: "thumbnailEnd",
225
+ thumbnailId
226
+ });
227
+ },
228
+ subscribe(listener) {
229
+ connectionListeners.add(listener);
230
+ return () => connectionListeners.delete(listener);
231
+ },
232
+ status: () => status,
233
+ subscribeStatus(listener) {
234
+ statusListeners.add(listener);
235
+ return () => statusListeners.delete(listener);
236
+ },
237
+ close() {
238
+ connectionListeners.clear();
239
+ statusListeners.clear();
240
+ for (const subscription of subscriptions) subscription.unsubscribe();
241
+ stopRelaying?.();
242
+ stopFollowing?.();
243
+ renderer = null;
244
+ }
245
+ };
246
+ }
247
+
248
+ //#endregion
249
+ export { createRendererHost };
@@ -0,0 +1,186 @@
1
+ import { referencePickerFocus } from "../preview/referencePickerFocus.js";
2
+ import { canvasStore } from "../canvas/canvasStore.js";
3
+ import { previewStore } from "../preview/previewStore.js";
4
+ import { SDK_VERSION, deferredInbox } from "./protocol.js";
5
+ import { answerFromCode } from "./definitionAnswers.js";
6
+
7
+ //#region src/features/renderer/studioConnection.ts
8
+ const mirrors = {
9
+ preview: previewStore,
10
+ canvas: canvasStore
11
+ };
12
+ const forwards = {
13
+ preview: previewStore,
14
+ referencePickerFocus
15
+ };
16
+ /**
17
+ * Connects the renderer document to the studio through its handle. This document's stores then
18
+ * mirror the studio's, its own events are forwarded to the studio, which owns their truth, and
19
+ * its cache follows the studio's (see `protocol.ts`). The studio's questions are answered from
20
+ * `camoxApp`, the draft's code; without it, none can be.
21
+ */
22
+ function connectToStudio(handle, { queryClient, studioCache, draftScope, camoxApp }) {
23
+ let welcomed = false;
24
+ let frames = [];
25
+ const listeners = /* @__PURE__ */ new Set();
26
+ let thumbnails = [];
27
+ const changed = () => {
28
+ for (const listener of listeners) listener();
29
+ };
30
+ const setFrames = (next) => {
31
+ frames = next;
32
+ changed();
33
+ };
34
+ const setThumbnails = (next) => {
35
+ thumbnails = next;
36
+ changed();
37
+ };
38
+ /** The edits sent to the studio that it hasn't answered yet. */
39
+ const editsInFlight = /* @__PURE__ */ new Map();
40
+ let lastEditId = 0;
41
+ /** Whether the renderer holds the read under `queryKey`. */
42
+ const holds = (queryKey) => !!queryClient.getQueryCache().find({
43
+ queryKey,
44
+ exact: true
45
+ });
46
+ const receive = (message) => {
47
+ if (message.type === "welcome") {
48
+ welcomed = message.protocol === 1 && message.sdkVersion === SDK_VERSION;
49
+ return;
50
+ }
51
+ if (!welcomed) return;
52
+ switch (message.type) {
53
+ case "frame": {
54
+ const frame = {
55
+ frameId: message.frameId,
56
+ slot: message.slot,
57
+ target: message.target
58
+ };
59
+ const index = frames.findIndex(({ frameId }) => frameId === message.frameId);
60
+ setFrames(index < 0 ? [...frames, frame] : frames.map((shown, at) => at === index ? frame : shown));
61
+ return;
62
+ }
63
+ case "frameEnd":
64
+ setFrames(frames.filter(({ frameId }) => frameId !== message.frameId));
65
+ return;
66
+ case "store":
67
+ mirrors[message.store]?.send({
68
+ type: "mirror",
69
+ context: message.context
70
+ });
71
+ return;
72
+ case "draftStarted":
73
+ draftScope.enterStarted(message.draftId);
74
+ return;
75
+ case "invalidate":
76
+ studioCache.receive(message.message, message.projectId);
77
+ return;
78
+ case "write":
79
+ if (!holds(message.queryKey)) return;
80
+ if ("value" in message && message.value !== void 0) queryClient.setQueryData(message.queryKey, message.value);
81
+ else queryClient.removeQueries({
82
+ queryKey: message.queryKey,
83
+ exact: true
84
+ });
85
+ return;
86
+ case "outdated":
87
+ for (const queryKey of message.queryKeys) if (holds(queryKey)) queryClient.invalidateQueries({
88
+ queryKey,
89
+ exact: true
90
+ });
91
+ return;
92
+ case "thumbnail": {
93
+ const { thumbnailId, slot, blockType } = message;
94
+ const thumbnail = {
95
+ thumbnailId,
96
+ slot,
97
+ blockType
98
+ };
99
+ const index = thumbnails.findIndex((shown) => shown.thumbnailId === thumbnailId);
100
+ setThumbnails(index < 0 ? [...thumbnails, thumbnail] : thumbnails.map((shown, at) => at === index ? thumbnail : shown));
101
+ return;
102
+ }
103
+ case "thumbnailEnd":
104
+ setThumbnails(thumbnails.filter(({ thumbnailId }) => thumbnailId !== message.thumbnailId));
105
+ return;
106
+ case "ask": {
107
+ const { askId, question } = message;
108
+ if (!camoxApp) {
109
+ studio({
110
+ type: "answer",
111
+ askId,
112
+ error: "The renderer has no code to answer with."
113
+ });
114
+ return;
115
+ }
116
+ answerFromCode(camoxApp, question).then((value) => studio({
117
+ type: "answer",
118
+ askId,
119
+ value
120
+ }), (error) => studio({
121
+ type: "answer",
122
+ askId,
123
+ error: error instanceof Error ? error.message : String(error)
124
+ }));
125
+ return;
126
+ }
127
+ case "edited": {
128
+ const edit = editsInFlight.get(message.editId);
129
+ if (!edit) return;
130
+ editsInFlight.delete(message.editId);
131
+ if (message.error === void 0) edit.resolve();
132
+ else edit.reject(new Error(message.error));
133
+ return;
134
+ }
135
+ }
136
+ };
137
+ const studio = handle.connect(deferredInbox(receive));
138
+ studio({
139
+ type: "hello",
140
+ protocol: 1,
141
+ sdkVersion: SDK_VERSION
142
+ });
143
+ const inspections = Object.keys(forwards).map((store) => forwards[store].inspect((inspection) => {
144
+ const event = inspection.event;
145
+ if (inspection.type !== "@xstate.transition" || !event) return;
146
+ if (event.type === "mirror" || event.type.startsWith("@xstate.")) return;
147
+ studio({
148
+ type: "storeEvent",
149
+ store,
150
+ event: { ...event }
151
+ });
152
+ }));
153
+ return {
154
+ connected: () => welcomed,
155
+ frames: () => frames,
156
+ thumbnails: () => thumbnails,
157
+ subscribe(listener) {
158
+ listeners.add(listener);
159
+ return () => listeners.delete(listener);
160
+ },
161
+ send: (message) => studio(message),
162
+ notify: (notice) => studio({
163
+ type: "notify",
164
+ notice
165
+ }),
166
+ edit: (edit) => new Promise((resolve, reject) => {
167
+ const editId = ++lastEditId;
168
+ editsInFlight.set(editId, {
169
+ resolve,
170
+ reject
171
+ });
172
+ studio({
173
+ type: "edit",
174
+ editId,
175
+ edit
176
+ });
177
+ }),
178
+ close() {
179
+ for (const inspection of inspections) inspection.unsubscribe();
180
+ listeners.clear();
181
+ }
182
+ };
183
+ }
184
+
185
+ //#endregion
186
+ export { connectToStudio };
@@ -1,11 +1,12 @@
1
1
  import { LocaleView } from "../../lib/locale-path.js";
2
+ import "../../core/layoutContext.js";
2
3
  import "../../core/structuredData.js";
3
4
  import "../../core/createLayout.js";
4
5
  import "../../core/createApp.js";
5
6
  import "../../lib/studio-reads.js";
6
- import { Router } from "@camox/api-contract";
7
7
  import { EndedDraft } from "@camox/api-contract/drafts";
8
8
  import { QueryClient } from "@tanstack/react-query";
9
+ import { Router } from "@camox/api-contract/router";
9
10
  import { RouterClient } from "@orpc/server";
10
11
  //#region src/features/routes/pageRuntime.d.ts
11
12
  type PageWithBlocks = Awaited<ReturnType<RouterClient<Router>["pages"]["getByPath"]>>;
@@ -2,10 +2,11 @@ import { UNNAMED_DEFAULT_LOCALE, localeHeaders } from "../../lib/locale-path.js"
2
2
  import { createDraftScope, withoutDraft } from "../../lib/draft-scope.js";
3
3
  import { createStudioCache, reads } from "../../lib/studio-reads.js";
4
4
  import { linkFallbackLocale } from "../../lib/link-pages.js";
5
- import { curatedLayoutContext } from "../../core/layoutContext.js";
6
5
  import { PAGE_IMAGE_SIZE, pageImageUrl } from "../og/pageImage.js";
6
+ import { curatedLayoutContext } from "../../core/layoutContext.js";
7
7
  import { isArticle } from "../../core/structuredData.js";
8
- import { DRAFT_ENDED_ERROR_CODE, DRAFT_HEADER, NO_DEVELOPMENT_DRAFT_ERROR } from "@camox/api-contract/drafts";
8
+ import { publishingDraft } from "../runtime/publishingDraft.js";
9
+ import { DRAFT_ENDED_ERROR_CODE, DRAFT_HEADER, NO_DEVELOPMENT_DRAFT_ERROR, PUBLISHING_DRAFT_HEADER } from "@camox/api-contract/drafts";
9
10
  import { createORPCClient } from "@orpc/client";
10
11
  import { RPCLink } from "@orpc/client/fetch";
11
12
 
@@ -38,6 +39,10 @@ function parseMarkdownPath(pathname) {
38
39
  function createServerApiClient(apiUrl, options) {
39
40
  const headers = localeHeaders(options?.locale);
40
41
  if (options?.draftId && options.authCookieHeader) headers[DRAFT_HEADER] = options.draftId;
42
+ else {
43
+ const publishing = publishingDraft();
44
+ if (publishing) headers[PUBLISHING_DRAFT_HEADER] = publishing;
45
+ }
41
46
  return createORPCClient(new RPCLink({
42
47
  url: `${apiUrl}/rpc`,
43
48
  headers,
@@ -218,25 +223,32 @@ async function loadCamoxPageForRequest({ apiUrl, authCookieHeader, draftId, loca
218
223
  shouldClearAuthCookie: member.clearAuthCookie
219
224
  };
220
225
  }
221
- async function createMarkdownResponse({ apiUrl, camoxApp, locale, pathname, projectUuid, request, url, onNotFound }) {
222
- const api = createServerApiClient(apiUrl, { locale });
223
- const requestPathname = pathname ?? new URL(request.url).pathname;
224
- try {
225
- const page = await api.pages.getByPath({
226
- path: requestPathname,
227
- projectUuid
228
- });
229
- const { markdown } = await api.blocks.getPageMarkdown({ pageId: page.page.id });
230
- if (!markdown) return null;
231
- const meta = buildPageMeta(curatedPageMetaInput(camoxApp, page, locale));
232
- return new Response(withFrontMatter(markdown, {
233
- ...meta,
234
- url
235
- }), { headers: { "Content-Type": "text/markdown; charset=utf-8" } });
236
- } catch (error) {
237
- if (onNotFound && isNotFoundError(error)) return onNotFound();
238
- return null;
239
- }
226
+ /**
227
+ * Reads the Markdown Representation of the page at a runtime path, with its locale's prefix, in
228
+ * one API read: the API finds the locale the prefix names, the page there, its Markdown and what
229
+ * its front matter is built from. Markdown is a public output, so it reads Live whoever asks.
230
+ */
231
+ function readPageMarkdown({ apiUrl, path, projectUuid }) {
232
+ return createServerApiClient(apiUrl).pages.getMarkdown({
233
+ path,
234
+ projectUuid
235
+ });
236
+ }
237
+ /**
238
+ * A curated page's Markdown Representation, opened by front matter stating its title,
239
+ * description and absolute `url`. Null when the path has no curated page, or the page has no
240
+ * Markdown.
241
+ */
242
+ function curatedMarkdownResponse(camoxApp, read, url) {
243
+ if (!read.page || !read.markdown) return null;
244
+ const meta = buildPageMeta(curatedPageMetaInput(camoxApp, {
245
+ ...read,
246
+ page: read.page
247
+ }, read.locale));
248
+ return new Response(withFrontMatter(read.markdown, {
249
+ ...meta,
250
+ url
251
+ }), { headers: { "Content-Type": "text/markdown; charset=utf-8" } });
240
252
  }
241
253
  /** A page's title and description, built by its layout's `toTitle` and `toDescription`. */
242
254
  function buildPageMeta(input) {
@@ -337,4 +349,4 @@ function buildCamoxPageHead(camoxApp, loaderData, locale) {
337
349
  }
338
350
 
339
351
  //#endregion
340
- export { buildCamoxPageHead, buildCamoxPageLoaderData, buildDefaultFaviconUrl, buildFaviconUrl, buildPageHead, buildPageMeta, createMarkdownResponse, createServerApiClient, createServerStudioCache, curatedPageMetaInput, declarePage, getMarkdownPath, isAuthSessionError, isNotFoundError, loadCamoxPage, loadCamoxPageForRequest, parseMarkdownPath, parseQuality, prefersMarkdown, readDraftOrLive, readScopeLocales, renderedHref, resolveMemberProject, withFrontMatter };
352
+ export { buildCamoxPageHead, buildCamoxPageLoaderData, buildDefaultFaviconUrl, buildFaviconUrl, buildPageHead, buildPageMeta, createServerApiClient, createServerStudioCache, curatedMarkdownResponse, curatedPageMetaInput, declarePage, getMarkdownPath, isAuthSessionError, isNotFoundError, loadCamoxPage, loadCamoxPageForRequest, parseMarkdownPath, parseQuality, prefersMarkdown, readDraftOrLive, readPageMarkdown, readScopeLocales, renderedHref, resolveMemberProject, withFrontMatter };
@@ -1,9 +1,9 @@
1
1
  import { previewStore, selectIsCanvasShown } from "../preview/previewStore.js";
2
2
  import { shownRecordPage } from "../preview/pageRecord.js";
3
+ import { StudioEditingFallback } from "../preview/components/StudioEditingFallback.js";
3
4
  import { Navbar } from "../studio/components/Navbar.js";
4
5
  import { EditingFallbackContext } from "./editingFallback.js";
5
6
  import { PreviewToolbar } from "../preview/components/PreviewToolbar.js";
6
- import { StudioEditingFallback } from "../preview/components/StudioEditingFallback.js";
7
7
  import { ChatSidebar } from "../studio/components/ChatSidebar.js";
8
8
  import { FollowAgentProvider } from "../studio/FollowAgentProvider.js";
9
9
  import { c } from "react/compiler-runtime";
@@ -12,10 +12,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
12
12
  import studioCssUrl from "virtual:camox-studio-css";
13
13
 
14
14
  //#region src/features/runtime/RuntimeChrome.tsx
15
- const renderEditingFallback = ({ layoutPage, children }) => /* @__PURE__ */ jsx(StudioEditingFallback, {
16
- layoutPage,
17
- children
18
- });
15
+ const renderEditingFallback = ({ layoutPage }) => /* @__PURE__ */ jsx(StudioEditingFallback, { layoutPage });
19
16
  function RuntimeChrome(t0) {
20
17
  const $ = c(32);
21
18
  const { input, children } = t0;
@@ -0,0 +1,14 @@
1
+ //#region src/features/runtime/assetPrefix.ts
2
+ /**
3
+ * One build serves under any URL prefix: Live at `/`, a revision's preview at
4
+ * `/_camox/r/<revisionId>/`. Whatever loads the server bundle sets this global first, never per
5
+ * request: the bundle collects its asset URLs when it loads. Unset, assets are under `/`.
6
+ */
7
+ const ASSET_PREFIX_GLOBAL = "__CAMOX_ASSET_PREFIX__";
8
+ /** The prefix the server build's assets are under, ending with `/`. */
9
+ function assetPrefix() {
10
+ return globalThis["__CAMOX_ASSET_PREFIX__"] ?? "/";
11
+ }
12
+
13
+ //#endregion
14
+ export { ASSET_PREFIX_GLOBAL, assetPrefix };
@@ -1,7 +1,7 @@
1
1
  import { recordPageValues } from "../../core/recordItem.js";
2
- import { curatedPageTitle } from "../../core/layoutContext.js";
3
2
  import { getPageDestinations } from "../../core/pageDestinations.js";
4
3
  import { matchDerivedLayout } from "../../core/derivedRoutes.js";
4
+ import { curatedPageTitle } from "../../core/layoutContext.js";
5
5
 
6
6
  //#region src/features/runtime/breadcrumbs.ts
7
7
  /** The paths above a page's, from the home page down: none for the home page itself. */
@@ -2,18 +2,14 @@ import * as React from "react";
2
2
 
3
3
  //#region src/features/runtime/editingFallback.tsx
4
4
  /**
5
- * Renders the read-only page while the editor is still loading. The Studio
5
+ * Renders what holds the page's place while the editor is still loading. The Studio
6
6
  * chrome provides it, so visitors never download it.
7
7
  */
8
8
  const EditingFallbackContext = React.createContext(null);
9
- /** The read-only page shown until the editor activates, laid out for the Canvas when it shows. */
10
- function EditingFallbackSlot({ layoutPage, children }) {
9
+ /** What shows until the editor activates, laid out for the Canvas when it shows. */
10
+ function EditingFallbackSlot({ layoutPage }) {
11
11
  const render = React.use(EditingFallbackContext);
12
- if (!render) return children;
13
- return render({
14
- layoutPage,
15
- children
16
- });
12
+ return render ? render({ layoutPage }) : null;
17
13
  }
18
14
 
19
15
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { createRequestContext } from "../../core/requestContext.js";
2
- import { curatedPageTitle } from "../../core/layoutContext.js";
3
2
  import { getPageDestinations } from "../../core/pageDestinations.js";
4
3
  import { discoverLayoutPages } from "../../core/derivedRoutes.js";
4
+ import { curatedPageTitle } from "../../core/layoutContext.js";
5
5
 
6
6
  //#region src/features/runtime/livePages.ts
7
7
  /**