camox 0.58.0 → 0.59.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.
- package/dist/core/createApp.d.ts +2 -2
- package/dist/core/createBlock.js +1 -1
- package/dist/core/createLayout.d.ts +1 -1
- package/dist/core/editing/createEditableBlock.d.ts +1 -1
- package/dist/core/editing/createEditableBlock.js +1 -1
- package/dist/core/lib/recordScope.js +1 -1
- package/dist/features/canvas/CamoxCanvas.js +2 -2
- package/dist/features/canvas/CanvasOverlays.js +1 -1
- package/dist/features/canvas/sidebar/LayoutPageSeo.js +1 -1
- package/dist/features/canvas/sidebar/PageInspector.js +1 -1
- package/dist/features/content/CamoxContent.js +1 -1
- package/dist/features/content/components/MissingFromLocale.js +1 -1
- package/dist/features/page/CuratedPageContent.js +1 -1
- package/dist/features/preview/CamoxPreview.js +3 -3
- package/dist/features/preview/components/AddBlockSheet.js +1 -1
- package/dist/features/preview/components/AssetLightbox.js +1 -1
- package/dist/features/preview/components/BlockActionsPopover.js +1 -1
- package/dist/features/preview/components/CreatePageModal.js +2 -2
- package/dist/features/preview/components/FieldOverlayStyles.js +1 -1
- package/dist/features/preview/components/MobilePreviewDrawer.js +1 -1
- package/dist/features/preview/components/Overlays.js +1 -1
- package/dist/features/preview/components/PageInfo.js +1 -1
- package/dist/features/preview/components/PagePicker.js +1 -1
- package/dist/features/preview/components/PageTree.js +1 -1
- package/dist/features/preview/components/PreviewPanel.js +1 -1
- package/dist/features/preview/components/PreviewToolbar.js +1 -1
- package/dist/features/preview/components/SelectionEditor.js +1 -1
- package/dist/features/preview/components/StudioEditingFallback.js +1 -1
- package/dist/features/provider/AuthenticatedCamoxProvider.js +1 -1
- package/dist/features/provider/CamoxProvider.js +1 -1
- package/dist/features/provider/CoreCamoxProvider.js +3 -3
- package/dist/features/provider/components/CommandPalette.js +1 -1
- package/dist/features/renderer/rendererHost.js +3 -7
- package/dist/features/routes/pageRuntime.d.ts +2 -5
- package/dist/features/routes/pageRuntime.js +1 -2
- package/dist/features/runtime/RuntimeChrome.js +1 -1
- package/dist/features/runtime/navigationTarget.js +1 -2
- package/dist/features/runtime/pageApp.js +1 -1
- package/dist/features/runtime/rendererClient.js +1 -1
- package/dist/features/runtime/runtime.d.ts +0 -5
- package/dist/features/runtime/runtime.js +28 -32
- package/dist/features/runtime/worker.js +3 -3
- package/dist/features/settings/FaviconSettings.js +1 -1
- package/dist/features/studio/CamoxStudio.js +1 -1
- package/dist/features/studio/FollowAgentProvider.js +1 -1
- package/dist/features/studio/components/ChatWidget.js +1 -1
- package/dist/features/studio/components/DraftsControl.js +232 -201
- package/dist/features/studio/components/LocalePicker.js +2 -2
- package/dist/features/studio/components/Navbar.js +3 -3
- package/dist/features/studio/components/Patrol.js +1 -1
- package/dist/features/studio/components/ProjectMenu.js +2 -2
- package/dist/features/studio/components/ReviewDraftSheet.js +9 -15
- package/dist/features/studio/components/ThreadMessages.js +103 -21
- package/dist/features/studio/components/UserButton.js +2 -2
- package/dist/features/studio/draftLabel.js +1 -2
- package/dist/features/studio/draftNotice.js +0 -1
- package/dist/features/studio/showInCanvas.js +1 -1
- package/dist/features/vite/definitionsSync.js +14 -60
- package/dist/features/vite/devSignIn.js +1 -1
- package/dist/features/vite/runtimeDev.js +0 -1
- package/dist/features/vite/vite.d.ts +8 -5
- package/dist/features/vite/vite.js +34 -25
- package/dist/lib/api-client-server.js +25 -8
- package/dist/lib/api-client.js +2 -6
- package/dist/lib/auth.js +1 -1
- package/dist/lib/draft-scope.js +5 -25
- package/dist/lib/preview-handoff.js +1 -1
- package/dist/lib/server-auth-cookie.js +12 -10
- package/dist/lib/studio-reads.js +1 -1
- package/dist/lib/use-thread.js +31 -35
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +4 -4
- package/skills/camox/references/block-definitions.md +1 -1
- package/dist/features/vite/publishSeededContent.js +0 -30
- package/skills/camox-cli/SKILL.md +0 -15
- package/skills/camox-cli/references/cli-common.md +0 -58
- package/skills/camox-cli/references/cli-content.md +0 -181
- package/skills/camox-cli/references/cli-publishing.md +0 -26
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AuthContext } from "../../../lib/auth.js";
|
|
2
1
|
import { localizedPath } from "../../../lib/locale-path.js";
|
|
3
2
|
import { useCurrentDraftId } from "../../../lib/draft-scope.js";
|
|
4
3
|
import { useViewLocale } from "../../../lib/view-locale-context.js";
|
|
5
4
|
import { useNavigate } from "../../navigation/navigation.js";
|
|
5
|
+
import { AuthContext } from "../../../lib/auth.js";
|
|
6
6
|
import { reads, useReadQuery } from "../../../lib/studio-reads.js";
|
|
7
7
|
import { cn } from "../../../lib/utils.js";
|
|
8
8
|
import { AssetLightbox } from "../../preview/components/AssetLightbox.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AuthContext } from "../../../lib/auth.js";
|
|
2
|
-
import { getApiUrl } from "../../../lib/api-client.js";
|
|
3
1
|
import { Link as Link$1 } from "../../navigation/navigation.js";
|
|
2
|
+
import { AuthContext } from "../../../lib/auth.js";
|
|
4
3
|
import { reads, useReadQuery } from "../../../lib/studio-reads.js";
|
|
4
|
+
import { getApiUrl } from "../../../lib/api-client.js";
|
|
5
5
|
import { useLocalizedPath } from "../../../lib/view-locale.js";
|
|
6
6
|
import { version } from "../../../package.js";
|
|
7
7
|
import { STUDIO_SETTINGS_PATH } from "../routes.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { getDraftReadClient } from "../../../lib/api-client.js";
|
|
2
1
|
import { useDraftScope } from "../../../lib/draft-scope.js";
|
|
3
2
|
import { reads, useReadQuery, useStudioCache } from "../../../lib/studio-reads.js";
|
|
4
3
|
import { cn } from "../../../lib/utils.js";
|
|
4
|
+
import { getDraftReadClient } from "../../../lib/api-client.js";
|
|
5
5
|
import { draftMutations } from "../../../lib/queries.js";
|
|
6
6
|
import { previewStore } from "../../preview/previewStore.js";
|
|
7
7
|
import { localeLabel } from "../localeOptions.js";
|
|
@@ -199,8 +199,7 @@ const SHARED_MESSAGE_MIN = 3;
|
|
|
199
199
|
const isShownWarning = (problem) => problem.code === "removedInUse";
|
|
200
200
|
/**
|
|
201
201
|
* One card per problem, saying what's wrong and where. Problems sharing a message fold into one
|
|
202
|
-
* card naming each: a
|
|
203
|
-
* block can sit on dozens of pages, with the same message for each. A problem on a page has an
|
|
202
|
+
* card naming each: a removed block can sit on dozens of pages, with the same message for each. A problem on a page has an
|
|
204
203
|
* action that shows it in the Canvas; a blocker in the draft's code, which only the agent fixes,
|
|
205
204
|
* one that opens the draft's thread.
|
|
206
205
|
*/
|
|
@@ -626,7 +625,7 @@ const publishInput = (projectId, shown, publishAnyway) => ({
|
|
|
626
625
|
});
|
|
627
626
|
/** Why a publish with code went back to the review, as the review says it. */
|
|
628
627
|
const PUBLISH_ABORTED = {
|
|
629
|
-
revisionChanged: "The
|
|
628
|
+
revisionChanged: "The draft's code changed since this review opened, so the publish stopped. Check the preview, then publish again.",
|
|
630
629
|
conflictsChanged: "Live changed while publishing, so the publish stopped and Live is as it was. Check the new list.",
|
|
631
630
|
blocked: "Something now needs fixing before publishing, so the publish stopped.",
|
|
632
631
|
codeDoesNotMerge: "Live's code changed while publishing, and it doesn't merge with this draft's yet, so the publish stopped. Camox's agent is fixing it in the draft's thread: publish again once it's done.",
|
|
@@ -963,17 +962,15 @@ function _temp8(bullet, index) {
|
|
|
963
962
|
function _temp7(error) {
|
|
964
963
|
return toast.error(error.message || "Could not summarize the draft");
|
|
965
964
|
}
|
|
966
|
-
/**
|
|
967
|
-
* Switches the studio to Live once its draft ended here, replacing the draft's URL entry. A
|
|
968
|
-
* development draft never ends: publishing or deleting empties it, and dev mode stays in it.
|
|
969
|
-
*/
|
|
965
|
+
/** Switches the studio to Live once its draft ended here, replacing the draft's URL entry. */
|
|
970
966
|
function useLeaveDraft() {
|
|
971
967
|
const $ = c(2);
|
|
972
968
|
const draftScope = useDraftScope();
|
|
973
969
|
let t0;
|
|
974
970
|
if ($[0] !== draftScope) {
|
|
975
971
|
t0 = (ended) => {
|
|
976
|
-
|
|
972
|
+
draftScope.leave(ended);
|
|
973
|
+
if (ended) toast.success(`Draft ${ended.outcome}. You're now viewing Live.`);
|
|
977
974
|
};
|
|
978
975
|
$[0] = draftScope;
|
|
979
976
|
$[1] = t0;
|
|
@@ -1358,14 +1355,11 @@ function ReviewDraftSheet(t0) {
|
|
|
1358
1355
|
className: "flex flex-col gap-1 border-b p-4 pr-12",
|
|
1359
1356
|
children: headline ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(SheetDescription, {
|
|
1360
1357
|
className: "flex min-w-0 items-center gap-1",
|
|
1361
|
-
children: [draft.data
|
|
1358
|
+
children: [draft.data && /* @__PURE__ */ jsx(DraftNameInput, {
|
|
1362
1359
|
ref: nameInput,
|
|
1363
1360
|
projectId,
|
|
1364
1361
|
draft: draft.data,
|
|
1365
1362
|
className: "placeholder:text-muted-foreground text-muted-foreground field-sizing-content h-6 w-auto max-w-full text-sm font-normal"
|
|
1366
|
-
}) : draft.data && /* @__PURE__ */ jsx("span", {
|
|
1367
|
-
className: "truncate",
|
|
1368
|
-
children: draftLabel(draft.data)
|
|
1369
1363
|
}), draft.data && /* @__PURE__ */ jsxs("span", {
|
|
1370
1364
|
className: "shrink-0",
|
|
1371
1365
|
children: [
|
|
@@ -1378,7 +1372,7 @@ function ReviewDraftSheet(t0) {
|
|
|
1378
1372
|
}), /* @__PURE__ */ jsx(SheetTitle, {
|
|
1379
1373
|
className: "text-lg leading-snug font-semibold",
|
|
1380
1374
|
children: headline
|
|
1381
|
-
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [draft.data
|
|
1375
|
+
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [draft.data ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SheetTitle, {
|
|
1382
1376
|
className: "sr-only",
|
|
1383
1377
|
children: draftLabel(draft.data)
|
|
1384
1378
|
}), /* @__PURE__ */ jsx(DraftNameInput, {
|
|
@@ -1387,7 +1381,7 @@ function ReviewDraftSheet(t0) {
|
|
|
1387
1381
|
draft: draft.data
|
|
1388
1382
|
})] }) : /* @__PURE__ */ jsx(SheetTitle, {
|
|
1389
1383
|
className: "text-base",
|
|
1390
|
-
children:
|
|
1384
|
+
children: "Draft"
|
|
1391
1385
|
}), /* @__PURE__ */ jsx(SheetDescription, { children: draft.data ? `${draft.data.createdBy.name} · started ${timeAgo(draft.data.createdAt)}` : "Publishing applies this whole draft to Live." })] })
|
|
1392
1386
|
});
|
|
1393
1387
|
$[49] = draft.data;
|
|
@@ -211,12 +211,18 @@ function squarelingStatus(status, usingTools) {
|
|
|
211
211
|
if (status === "fixing") return "fixing";
|
|
212
212
|
return status === "building" || usingTools ? "acting" : "thinking";
|
|
213
213
|
}
|
|
214
|
+
/** How a member's pushed code built, as the thread says it. */
|
|
215
|
+
const PUSH_OUTCOMES = {
|
|
216
|
+
built: "Built, and in the preview.",
|
|
217
|
+
typeErrors: "Built, with type errors that block publishing.",
|
|
218
|
+
notPreviewed: "Built, but the preview can’t show it."
|
|
219
|
+
};
|
|
214
220
|
const STATUS_LINES = {
|
|
215
221
|
building: "Building…",
|
|
216
222
|
fixing: "Fixing a build error…"
|
|
217
223
|
};
|
|
218
224
|
function ThreadMessageRow(t0) {
|
|
219
|
-
const $ = c(
|
|
225
|
+
const $ = c(36);
|
|
220
226
|
const { message, onOpenDraft } = t0;
|
|
221
227
|
if (message.from === "draft") {
|
|
222
228
|
let t1;
|
|
@@ -253,17 +259,87 @@ function ThreadMessageRow(t0) {
|
|
|
253
259
|
} else t1 = $[6];
|
|
254
260
|
return t1;
|
|
255
261
|
}
|
|
262
|
+
if (message.from === "push") {
|
|
263
|
+
const commits = message.subjects.length;
|
|
264
|
+
let t1;
|
|
265
|
+
if ($[7] !== message.sender) {
|
|
266
|
+
t1 = /* @__PURE__ */ jsx(MessageAvatar, { children: /* @__PURE__ */ jsx(SenderAvatar, { sender: message.sender }) });
|
|
267
|
+
$[7] = message.sender;
|
|
268
|
+
$[8] = t1;
|
|
269
|
+
} else t1 = $[8];
|
|
270
|
+
const t2 = `${message.sender.name} pushed ${commits} ${commits === 1 ? "commit" : "commits"}`;
|
|
271
|
+
let t3;
|
|
272
|
+
if ($[9] !== t2) {
|
|
273
|
+
t3 = /* @__PURE__ */ jsx(MessageHeader, {
|
|
274
|
+
className: "text-muted-foreground text-xs",
|
|
275
|
+
children: t2
|
|
276
|
+
});
|
|
277
|
+
$[9] = t2;
|
|
278
|
+
$[10] = t3;
|
|
279
|
+
} else t3 = $[10];
|
|
280
|
+
let t4;
|
|
281
|
+
if ($[11] !== message.subjects) {
|
|
282
|
+
t4 = message.subjects.map(_temp);
|
|
283
|
+
$[11] = message.subjects;
|
|
284
|
+
$[12] = t4;
|
|
285
|
+
} else t4 = $[12];
|
|
286
|
+
let t5;
|
|
287
|
+
if ($[13] !== t4) {
|
|
288
|
+
t5 = /* @__PURE__ */ jsx("ul", {
|
|
289
|
+
className: "flex flex-col gap-0.5",
|
|
290
|
+
children: t4
|
|
291
|
+
});
|
|
292
|
+
$[13] = t4;
|
|
293
|
+
$[14] = t5;
|
|
294
|
+
} else t5 = $[14];
|
|
295
|
+
const t6 = PUSH_OUTCOMES[message.outcome];
|
|
296
|
+
let t7;
|
|
297
|
+
if ($[15] !== t6) {
|
|
298
|
+
t7 = /* @__PURE__ */ jsx("span", {
|
|
299
|
+
className: "text-muted-foreground text-xs",
|
|
300
|
+
children: t6
|
|
301
|
+
});
|
|
302
|
+
$[15] = t6;
|
|
303
|
+
$[16] = t7;
|
|
304
|
+
} else t7 = $[16];
|
|
305
|
+
let t8;
|
|
306
|
+
if ($[17] !== t5 || $[18] !== t7) {
|
|
307
|
+
t8 = /* @__PURE__ */ jsxs("div", {
|
|
308
|
+
"data-thread-push": true,
|
|
309
|
+
className: DRAFT_CARD_CLASSES,
|
|
310
|
+
children: [t5, t7]
|
|
311
|
+
});
|
|
312
|
+
$[17] = t5;
|
|
313
|
+
$[18] = t7;
|
|
314
|
+
$[19] = t8;
|
|
315
|
+
} else t8 = $[19];
|
|
316
|
+
let t9;
|
|
317
|
+
if ($[20] !== t3 || $[21] !== t8) {
|
|
318
|
+
t9 = /* @__PURE__ */ jsxs(MessageContent, { children: [t3, t8] });
|
|
319
|
+
$[20] = t3;
|
|
320
|
+
$[21] = t8;
|
|
321
|
+
$[22] = t9;
|
|
322
|
+
} else t9 = $[22];
|
|
323
|
+
let t10;
|
|
324
|
+
if ($[23] !== t1 || $[24] !== t9) {
|
|
325
|
+
t10 = /* @__PURE__ */ jsxs(Message, { children: [t1, t9] });
|
|
326
|
+
$[23] = t1;
|
|
327
|
+
$[24] = t9;
|
|
328
|
+
$[25] = t10;
|
|
329
|
+
} else t10 = $[25];
|
|
330
|
+
return t10;
|
|
331
|
+
}
|
|
256
332
|
if (message.from === "camox") {
|
|
257
333
|
let t1;
|
|
258
|
-
if ($[
|
|
334
|
+
if ($[26] === Symbol.for("react.memo_cache_sentinel")) {
|
|
259
335
|
t1 = /* @__PURE__ */ jsx(MessageHeader, {
|
|
260
336
|
className: "text-muted-foreground text-xs",
|
|
261
337
|
children: "Camox"
|
|
262
338
|
});
|
|
263
|
-
$[
|
|
264
|
-
} else t1 = $[
|
|
339
|
+
$[26] = t1;
|
|
340
|
+
} else t1 = $[26];
|
|
265
341
|
let t2;
|
|
266
|
-
if ($[
|
|
342
|
+
if ($[27] !== message.text) {
|
|
267
343
|
t2 = /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsxs(MessageContent, { children: [t1, /* @__PURE__ */ jsx(Bubble, {
|
|
268
344
|
variant: "muted",
|
|
269
345
|
className: "max-w-full",
|
|
@@ -272,19 +348,19 @@ function ThreadMessageRow(t0) {
|
|
|
272
348
|
children: message.text
|
|
273
349
|
})
|
|
274
350
|
})] }) });
|
|
275
|
-
$[
|
|
276
|
-
$[
|
|
277
|
-
} else t2 = $[
|
|
351
|
+
$[27] = message.text;
|
|
352
|
+
$[28] = t2;
|
|
353
|
+
} else t2 = $[28];
|
|
278
354
|
return t2;
|
|
279
355
|
}
|
|
280
356
|
let t1;
|
|
281
|
-
if ($[
|
|
357
|
+
if ($[29] !== message.sender) {
|
|
282
358
|
t1 = /* @__PURE__ */ jsx(MessageAvatar, { children: /* @__PURE__ */ jsx(SenderAvatar, { sender: message.sender }) });
|
|
283
|
-
$[
|
|
284
|
-
$[
|
|
285
|
-
} else t1 = $[
|
|
359
|
+
$[29] = message.sender;
|
|
360
|
+
$[30] = t1;
|
|
361
|
+
} else t1 = $[30];
|
|
286
362
|
let t2;
|
|
287
|
-
if ($[
|
|
363
|
+
if ($[31] !== message.text) {
|
|
288
364
|
t2 = /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(Bubble, {
|
|
289
365
|
align: "end",
|
|
290
366
|
variant: "muted",
|
|
@@ -294,21 +370,27 @@ function ThreadMessageRow(t0) {
|
|
|
294
370
|
children: message.text
|
|
295
371
|
})
|
|
296
372
|
}) });
|
|
297
|
-
$[
|
|
298
|
-
$[
|
|
299
|
-
} else t2 = $[
|
|
373
|
+
$[31] = message.text;
|
|
374
|
+
$[32] = t2;
|
|
375
|
+
} else t2 = $[32];
|
|
300
376
|
let t3;
|
|
301
|
-
if ($[
|
|
377
|
+
if ($[33] !== t1 || $[34] !== t2) {
|
|
302
378
|
t3 = /* @__PURE__ */ jsxs(Message, {
|
|
303
379
|
align: "end",
|
|
304
380
|
children: [t1, t2]
|
|
305
381
|
});
|
|
306
|
-
$[
|
|
307
|
-
$[
|
|
308
|
-
$[
|
|
309
|
-
} else t3 = $[
|
|
382
|
+
$[33] = t1;
|
|
383
|
+
$[34] = t2;
|
|
384
|
+
$[35] = t3;
|
|
385
|
+
} else t3 = $[35];
|
|
310
386
|
return t3;
|
|
311
387
|
}
|
|
388
|
+
function _temp(subject, index) {
|
|
389
|
+
return /* @__PURE__ */ jsx("li", {
|
|
390
|
+
className: "font-mono text-xs",
|
|
391
|
+
children: subject
|
|
392
|
+
}, index);
|
|
393
|
+
}
|
|
312
394
|
const initials = (name) => name.split(" ").map((part) => part[0]).join("").toUpperCase().slice(0, 2);
|
|
313
395
|
const DRAFT_CARD_CLASSES = "bg-muted/50 flex w-full flex-col items-start gap-0.5 rounded-lg border px-3 py-2 text-left text-sm";
|
|
314
396
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useAuthContext, useAuthState } from "../../../lib/auth.js";
|
|
2
1
|
import { useNavigate } from "../../navigation/navigation.js";
|
|
2
|
+
import { useAuthContext, useAuthState } from "../../../lib/auth.js";
|
|
3
3
|
import { useLocalePath, useLocalizedPath } from "../../../lib/view-locale.js";
|
|
4
4
|
import { previewStore, selectIsCanvasShown } from "../../preview/previewStore.js";
|
|
5
5
|
import { PlatformLabel } from "../../../components/PlatformLabel.js";
|
|
@@ -7,8 +7,8 @@ import { STUDIO_BASE_PATH } from "../routes.js";
|
|
|
7
7
|
import { studioStore } from "../studioStore.js";
|
|
8
8
|
import { useApplyTheme } from "../useTheme.js";
|
|
9
9
|
import { c } from "react/compiler-runtime";
|
|
10
|
-
import { useSelector } from "@xstate/store-react";
|
|
11
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { useSelector } from "@xstate/store-react";
|
|
12
12
|
import { Button } from "@camox/ui/button";
|
|
13
13
|
import { Check, Command, EyeOff, LogOut, Monitor, Moon, Settings, Sun, User } from "lucide-react";
|
|
14
14
|
import { Avatar, AvatarFallback, AvatarImage } from "@camox/ui/avatar";
|
|
@@ -5,10 +5,9 @@ const dayFormat = new Intl.DateTimeFormat(void 0, {
|
|
|
5
5
|
});
|
|
6
6
|
/**
|
|
7
7
|
* A draft's name, or for an unnamed draft its creator and creation date formatted for the
|
|
8
|
-
* viewer, e.g. "Rémi, Oct 13".
|
|
8
|
+
* viewer, e.g. "Rémi, Oct 13".
|
|
9
9
|
*/
|
|
10
10
|
function draftLabel(draft) {
|
|
11
|
-
if (draft.kind === "development") return "Development draft";
|
|
12
11
|
if (draft.name) return draft.name;
|
|
13
12
|
return `${draft.createdBy.name}, ${dayFormat.format(draft.createdAt)}`;
|
|
14
13
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
function draftNoticeMessage(notice) {
|
|
4
4
|
switch (notice.reason) {
|
|
5
5
|
case "missing": return "This draft doesn't exist, so you're viewing Live.";
|
|
6
|
-
case "noDevelopmentDraft": return "Your development draft doesn't exist until the dev server syncs your definitions, so you're viewing Live.";
|
|
7
6
|
case "forbidden": return "You can't open this draft, so you're viewing Live.";
|
|
8
7
|
case "ended": return `${notice.endedBy.name} ${notice.outcome} this draft, so you're viewing Live.`;
|
|
9
8
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useProjectUuid } from "../../lib/auth.js";
|
|
2
1
|
import { localizedPath } from "../../lib/locale-path.js";
|
|
3
2
|
import { useViewLocale } from "../../lib/view-locale-context.js";
|
|
4
3
|
import { useLocation, useNavigate } from "../navigation/navigation.js";
|
|
4
|
+
import { useProjectUuid } from "../../lib/auth.js";
|
|
5
5
|
import { reads, useStudioCache } from "../../lib/studio-reads.js";
|
|
6
6
|
import { previewStore, sameEditingOwner } from "../preview/previewStore.js";
|
|
7
7
|
import { fieldTypesDictionary } from "../../core/lib/fieldTypes.js";
|
|
@@ -1,32 +1,14 @@
|
|
|
1
1
|
import { createServerApiClient } from "../../lib/api-client-server.js";
|
|
2
|
-
import { DEVELOPMENT_DRAFT } from "@camox/api-contract/drafts";
|
|
3
2
|
import { isRunnableDevEnvironment } from "vite-plus";
|
|
4
3
|
|
|
5
4
|
//#region src/features/vite/definitionsSync.ts
|
|
6
5
|
const SYNC_DEBOUNCE_DELAY_MS = 100;
|
|
7
|
-
/**
|
|
8
|
-
* The client definitions sync with. A release (deploy token) syncs Live's definitions; dev mode
|
|
9
|
-
* sends the developer's session and syncs their development draft, which its first sync
|
|
10
|
-
* creates (ADR 0006).
|
|
11
|
-
*/
|
|
12
|
-
function createSyncClient(options) {
|
|
13
|
-
const draftId = options.deployToken ? void 0 : DEVELOPMENT_DRAFT;
|
|
14
|
-
return createServerApiClient(options.apiUrl, options.authToken, draftId);
|
|
15
|
-
}
|
|
16
6
|
function throwIfSyncAuthError(error) {
|
|
17
7
|
if (error instanceof Error && error.name === "ORPCError" && error.message.toLowerCase().includes("unauthorized")) throw new Error("[camox] Definition sync failed: invalid or missing credentials.");
|
|
18
8
|
}
|
|
19
9
|
function isNotFoundError(error) {
|
|
20
10
|
return error instanceof Error && error.name === "ORPCError" && error.message.toLowerCase().includes("not found");
|
|
21
11
|
}
|
|
22
|
-
/**
|
|
23
|
-
* A sync that may not create the development draft finds nothing to write to: a release names a
|
|
24
|
-
* project that doesn't exist, or dev mode has no development draft yet (ADR 0006).
|
|
25
|
-
*/
|
|
26
|
-
function throwSyncTargetNotFoundError(projectUuid, deployToken) {
|
|
27
|
-
if (deployToken) throw new Error(`[camox] No project has the Project ID "${projectUuid}". Check the camox() projectId.`);
|
|
28
|
-
throw new Error(`[camox] You have no development draft in the project with Project ID "${projectUuid}" yet. Run the dev server while authenticated to create it first.`);
|
|
29
|
-
}
|
|
30
12
|
/** `count` of `noun`, pluralized: "1 block", "2 blocks". */
|
|
31
13
|
function counted(count, noun) {
|
|
32
14
|
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
@@ -36,26 +18,25 @@ function quoted(names) {
|
|
|
36
18
|
return names.map((name) => `"${name}"`).join(", ");
|
|
37
19
|
}
|
|
38
20
|
/**
|
|
39
|
-
*
|
|
40
|
-
* It goes in one `definitions.sync` call, which either fully applies it or changes
|
|
21
|
+
* Release a Definition Set to Live with the Deploy Token, adding the content it declares that
|
|
22
|
+
* Live lacks. It goes in one `definitions.sync` call, which either fully applies it or changes
|
|
23
|
+
* nothing.
|
|
41
24
|
*/
|
|
42
25
|
async function syncDefinitionsToApi(options) {
|
|
43
|
-
const { definitionSet, projectUuid,
|
|
44
|
-
const client =
|
|
26
|
+
const { definitionSet, projectUuid, apiUrl, deployToken, logger } = options;
|
|
27
|
+
const client = createServerApiClient(apiUrl);
|
|
45
28
|
let result;
|
|
46
29
|
try {
|
|
47
30
|
result = await client.definitions.sync({
|
|
48
31
|
projectUuid,
|
|
49
32
|
deployToken,
|
|
50
|
-
autoCreate,
|
|
51
33
|
definitionSet
|
|
52
34
|
});
|
|
53
35
|
} catch (error) {
|
|
54
36
|
throwIfSyncAuthError(error);
|
|
55
|
-
if (
|
|
37
|
+
if (isNotFoundError(error)) throw new Error(`[camox] No project has the Project ID "${projectUuid}". Check the camox() projectId.`);
|
|
56
38
|
throw error;
|
|
57
39
|
}
|
|
58
|
-
if (result.draftCreated) logger.info("[camox] Created your development draft", { timestamp: true });
|
|
59
40
|
const definitionCount = result.blocks.count + result.collections.count + (result.layouts?.count ?? 0);
|
|
60
41
|
logger.info(`[camox] Synced ${counted(definitionCount, "Camox definition")}`, { timestamp: true });
|
|
61
42
|
const { deletedDefinitionTypes } = result.blocks;
|
|
@@ -130,53 +111,29 @@ function isAppDependency(server, file) {
|
|
|
130
111
|
function isScriptFile(file) {
|
|
131
112
|
return /\.[cm]?[jt]sx?$/.test(file) && !file.endsWith(".d.ts");
|
|
132
113
|
}
|
|
133
|
-
/** Whether a project's Live has no pages, as on a fresh database. */
|
|
134
|
-
async function liveHasNoPages(apiUrl, projectUuid) {
|
|
135
|
-
const { pages } = await createServerApiClient(apiUrl).pages.listBySlug({ projectUuid });
|
|
136
|
-
return pages.length === 0;
|
|
137
|
-
}
|
|
138
114
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* and `releasedToLive` if it first released them to an empty Live (`releaseToEmptyLive`).
|
|
115
|
+
* Release the app's whole Definition Set to Live now, then again whenever a module the app depends
|
|
116
|
+
* on changes (debounced), skipping a release when the set equals the last one sent (ADR 0027).
|
|
117
|
+
* Resolves once the initial release settles: `synced` if definitions reached the API.
|
|
143
118
|
*/
|
|
144
119
|
async function syncDefinitions(server, options) {
|
|
145
|
-
const { projectUuid, apiUrl, autoCreate, authToken, deployToken, releaseToEmptyLive } = options;
|
|
146
120
|
const logger = server.config.logger;
|
|
147
|
-
/** What
|
|
121
|
+
/** What Live last received, so an unchanged set isn't sent again. */
|
|
148
122
|
let lastSynced;
|
|
149
|
-
|
|
150
|
-
async function sync({ initial = false } = {}) {
|
|
123
|
+
async function sync() {
|
|
151
124
|
const definitionSet = await loadDefinitionSet(server);
|
|
152
125
|
const serialized = serializeDefinitionSet(definitionSet);
|
|
153
126
|
if (serialized === lastSynced) return;
|
|
154
|
-
if (initial && releaseToEmptyLive && await liveHasNoPages(apiUrl, projectUuid)) {
|
|
155
|
-
logger.info("[camox] Live is empty: releasing your definitions to it", { timestamp: true });
|
|
156
|
-
await syncDefinitionsToApi({
|
|
157
|
-
definitionSet,
|
|
158
|
-
projectUuid,
|
|
159
|
-
apiUrl,
|
|
160
|
-
autoCreate: false,
|
|
161
|
-
deployToken: releaseToEmptyLive,
|
|
162
|
-
logger
|
|
163
|
-
});
|
|
164
|
-
releasedToLive = true;
|
|
165
|
-
}
|
|
166
127
|
await syncDefinitionsToApi({
|
|
128
|
+
...options,
|
|
167
129
|
definitionSet,
|
|
168
|
-
projectUuid,
|
|
169
|
-
apiUrl,
|
|
170
|
-
autoCreate,
|
|
171
|
-
authToken,
|
|
172
|
-
deployToken,
|
|
173
130
|
logger
|
|
174
131
|
});
|
|
175
132
|
lastSynced = serialized;
|
|
176
133
|
}
|
|
177
134
|
let synced = false;
|
|
178
135
|
try {
|
|
179
|
-
await sync(
|
|
136
|
+
await sync();
|
|
180
137
|
synced = true;
|
|
181
138
|
} catch (error) {
|
|
182
139
|
logger.error(`[camox] Failed to sync Camox definitions: ${String(error)}`, { timestamp: true });
|
|
@@ -200,10 +157,7 @@ async function syncDefinitions(server, options) {
|
|
|
200
157
|
server.watcher.on("unlink", (file) => {
|
|
201
158
|
if (isScriptFile(file) || isAppDependency(server, file)) scheduleSync();
|
|
202
159
|
});
|
|
203
|
-
return {
|
|
204
|
-
synced,
|
|
205
|
-
releasedToLive
|
|
206
|
-
};
|
|
160
|
+
return { synced };
|
|
207
161
|
}
|
|
208
162
|
|
|
209
163
|
//#endregion
|
|
@@ -58,7 +58,7 @@ function installDevSignInLink(server, options) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
if (controller.signal.aborted) return;
|
|
61
|
-
server.config.logger.warn("Camox sign-in link unavailable. Use the normal local URL to sign in, or
|
|
61
|
+
server.config.logger.warn("Camox sign-in link unavailable. Use the normal local URL to sign in, or restart the dev server for a fresh link.");
|
|
62
62
|
}
|
|
63
63
|
async function openSignInLink(url) {
|
|
64
64
|
let warned = false;
|
|
@@ -231,7 +231,6 @@ const options = {
|
|
|
231
231
|
pageClientEntryUrl,
|
|
232
232
|
studioClientEntryUrl,
|
|
233
233
|
rendererClientEntryUrl,
|
|
234
|
-
developmentDraft: __CAMOX_DEVELOPMENT_DRAFT__,
|
|
235
234
|
getCamoxApp: async () => camoxApp,
|
|
236
235
|
getDocument: async () => camoxDocument,
|
|
237
236
|
// Loaded by the first page image, so the stylesheet stays out of the Worker's main chunk.
|
|
@@ -3,7 +3,11 @@ import { Logger, Plugin } from "vite-plus";
|
|
|
3
3
|
//#region src/features/vite/vite.d.ts
|
|
4
4
|
/** What `_internal.afterSync` receives: an API client authenticated as the dev user. */
|
|
5
5
|
export interface CamoxAfterSyncContext {
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Sends the dev session. Its first content write starts a regular draft, as an edit in the
|
|
8
|
+
* studio does, and it reads and writes that draft from then on. Until then it reads Live, so
|
|
9
|
+
* seeding that finds its content starts nothing.
|
|
10
|
+
*/
|
|
7
11
|
client: ServerApiClient;
|
|
8
12
|
/** The project's Project ID, which API inputs name `projectUuid`. */
|
|
9
13
|
projectUuid: string;
|
|
@@ -45,11 +49,10 @@ export interface CamoxPluginOptions {
|
|
|
45
49
|
*/
|
|
46
50
|
afterSync?: (context: CamoxAfterSyncContext) => void | Promise<void>;
|
|
47
51
|
/**
|
|
48
|
-
* Dev-only:
|
|
49
|
-
* releases the
|
|
50
|
-
* `afterSync`, so seeded content becomes Live instead of the draft's changes.
|
|
52
|
+
* Dev-only: the local API's Deploy Token. Dev mode runs only against a local API with it,
|
|
53
|
+
* and releases the app's definitions to local Live on every save (ADR 0027).
|
|
51
54
|
*/
|
|
52
|
-
|
|
55
|
+
localDeployToken?: string;
|
|
53
56
|
};
|
|
54
57
|
}
|
|
55
58
|
export declare const CAMOX_OPTIMIZED_DEPS: string[];
|
|
@@ -13,11 +13,9 @@ import { generateLayoutFiles, watchLayoutFiles } from "./layoutGeneration.js";
|
|
|
13
13
|
import { publicFilesInHashedDirs, readOwnHeaders, staticAssetHeaders, unhashedEntries } from "./staticAssetHeaders.js";
|
|
14
14
|
import { buildClientThenWorker } from "./workerBuild.js";
|
|
15
15
|
import { VIRTUAL_CAMOX_APP, syncDefinitions, syncDefinitionsToApi } from "./definitionsSync.js";
|
|
16
|
-
import { publishSeededContent } from "./publishSeededContent.js";
|
|
17
16
|
import { cleanupGeneratedRouteFiles } from "./routeGeneration.js";
|
|
18
17
|
import { RESOLVED_VIRTUAL_DEV_HTML, createRuntimeRegistryPlugin, loadRuntimeDevModule, resolveRuntimeDevId } from "./runtimeDev.js";
|
|
19
|
-
import { projectIdSchema } from "@camox/api-contract/ids";
|
|
20
|
-
import { DEVELOPMENT_DRAFT } from "@camox/api-contract/drafts";
|
|
18
|
+
import { generateUuidv7, projectIdSchema } from "@camox/api-contract/ids";
|
|
21
19
|
import { createHash, randomUUID } from "node:crypto";
|
|
22
20
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
23
21
|
import { dirname, join, posix, resolve } from "node:path";
|
|
@@ -62,8 +60,7 @@ function findClientOnLog(config) {
|
|
|
62
60
|
* CLI can pick up the same Project ID / apiUrl / authenticationUrl the
|
|
63
61
|
* plugin actually used. The CLI treats the vite config as the source of
|
|
64
62
|
* truth for those values — there is no other reliable way to recover them
|
|
65
|
-
* from outside vite.
|
|
66
|
-
* developer's development draft (ADR 0010).
|
|
63
|
+
* from outside vite.
|
|
67
64
|
*/
|
|
68
65
|
function writeRuntimeSidecar(root, data) {
|
|
69
66
|
const dir = join(root, "node_modules", ".camox");
|
|
@@ -170,6 +167,20 @@ function projectIdOf(options) {
|
|
|
170
167
|
if (!projectIdSchema.safeParse(options.projectId).success) throw new Error(`Camox: ${JSON.stringify(options.projectId)} is not a Project ID. Copy your project's Project ID, a lowercase UUIDv7, from the Camox dashboard into your camox() config.`);
|
|
171
168
|
return options.projectId;
|
|
172
169
|
}
|
|
170
|
+
/** Whether an API URL is on this machine, as each checkout's own local API is. */
|
|
171
|
+
function isLocalApi(apiUrl) {
|
|
172
|
+
const { hostname } = new URL(apiUrl);
|
|
173
|
+
return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The Deploy Token dev mode releases with, refused when it's missing or the API isn't local:
|
|
177
|
+
* dev mode exists for Camox contributors, and a hosted project's code never comes from a machine.
|
|
178
|
+
*/
|
|
179
|
+
function localDeployTokenOf(apiUrl, options) {
|
|
180
|
+
const token = options._internal?.localDeployToken;
|
|
181
|
+
if (token && isLocalApi(apiUrl)) return token;
|
|
182
|
+
throw new Error("Camox: dev mode only runs against a local Camox API, with its internal Deploy Token. A hosted project's code reaches Camox through drafts and Upgrades.");
|
|
183
|
+
}
|
|
173
184
|
function camox(options) {
|
|
174
185
|
const projectUuid = projectIdOf(options);
|
|
175
186
|
const apiUrl = options._internal?.apiUrl ?? PRODUCTION_API_URL;
|
|
@@ -185,6 +196,8 @@ function camox(options) {
|
|
|
185
196
|
let hostedProject;
|
|
186
197
|
let resolvedConfig;
|
|
187
198
|
let localAuth = null;
|
|
199
|
+
/** Set in dev mode, which releases to local Live with it. */
|
|
200
|
+
let localDeployToken;
|
|
188
201
|
let blockChunks = {};
|
|
189
202
|
let devServer;
|
|
190
203
|
return {
|
|
@@ -264,10 +277,8 @@ function camox(options) {
|
|
|
264
277
|
const release = process.env.CAMOX_INTERNAL_RELEASE;
|
|
265
278
|
isRelease = isBuild && (release === "1" || release === "dry-run");
|
|
266
279
|
isDryRun = isRelease && release === "dry-run";
|
|
267
|
-
if (!
|
|
268
|
-
|
|
269
|
-
if (isBuild && !localAuth) throw new Error("Camox: not authenticated. Run `npx camox login` before building the site.");
|
|
270
|
-
}
|
|
280
|
+
if (env.command === "serve" && !env.isPreview) localDeployToken = localDeployTokenOf(apiUrl, options);
|
|
281
|
+
if (!isBuild) localAuth = readAuthTokenForUrl(authenticationUrl);
|
|
271
282
|
return {
|
|
272
283
|
...isBuild && {
|
|
273
284
|
base: "./",
|
|
@@ -277,14 +288,16 @@ function camox(options) {
|
|
|
277
288
|
return { runtime: serverAssetUrl(filename) };
|
|
278
289
|
} }
|
|
279
290
|
},
|
|
280
|
-
build: {
|
|
291
|
+
build: {
|
|
292
|
+
emitAssets: true,
|
|
293
|
+
rolldownOptions: { experimental: { lazyBarrel: true } }
|
|
294
|
+
},
|
|
281
295
|
..._config.builder?.buildApp ? {} : { builder: { buildApp: buildClientThenWorker } },
|
|
282
296
|
define: {
|
|
283
297
|
__CAMOX_ICON_IDS__: JSON.stringify(iconIds),
|
|
284
298
|
__CAMOX_TELEMETRY_DISABLED__: JSON.stringify(!!options.disableTelemetry),
|
|
285
299
|
__ENABLE_TANSTACK_DEVTOOLS__: JSON.stringify(enableTanstackDevtools),
|
|
286
300
|
__CAMOX_ENABLE_EXPERIMENTAL_FEATURES__: JSON.stringify(options._internal?.enableExperimentalFeatures ?? false),
|
|
287
|
-
__CAMOX_DEVELOPMENT_DRAFT__: JSON.stringify(!isRelease),
|
|
288
301
|
__CAMOX_API_URL__: JSON.stringify(apiUrl),
|
|
289
302
|
__CAMOX_AUTHENTICATION_URL__: JSON.stringify(authenticationUrl),
|
|
290
303
|
__CAMOX_PROJECT_ID__: JSON.stringify(projectUuid),
|
|
@@ -342,10 +355,11 @@ function camox(options) {
|
|
|
342
355
|
}
|
|
343
356
|
if (disableCodeGen) config.logger.warn("⚠️ Code generation is disabled (_internal.disableCodeGen). This option is only meant for momentary debugging — do not deploy or commit your app with it enabled.", { timestamp: true });
|
|
344
357
|
const mode = config.command === "serve" ? "Running" : "Building";
|
|
345
|
-
let target = "
|
|
358
|
+
let target = "Live, not released";
|
|
346
359
|
if (isDryRun) target = "Live, dry run";
|
|
347
360
|
else if (isRelease) target = "Live";
|
|
348
|
-
else if (
|
|
361
|
+
else if (localDeployToken) target = "local Live";
|
|
362
|
+
else if (!isBuild && !localAuth) target = "authentication required";
|
|
349
363
|
config.logger.info(`${mode} Camox app (${target})`, { timestamp: true });
|
|
350
364
|
},
|
|
351
365
|
configureServer(server) {
|
|
@@ -369,28 +383,24 @@ function camox(options) {
|
|
|
369
383
|
watchNewBlockFiles(server);
|
|
370
384
|
}
|
|
371
385
|
server.httpServer?.once("listening", () => {
|
|
372
|
-
if (!
|
|
373
|
-
const authToken = localAuth.token;
|
|
386
|
+
if (!localDeployToken) return;
|
|
374
387
|
syncDefinitions(server, {
|
|
375
388
|
projectUuid,
|
|
376
389
|
apiUrl,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (!synced) return;
|
|
390
|
+
deployToken: localDeployToken
|
|
391
|
+
}).then(async ({ synced }) => {
|
|
392
|
+
const afterSync = options._internal?.afterSync;
|
|
393
|
+
if (!synced || !afterSync || !localAuth) return;
|
|
382
394
|
const context = {
|
|
383
|
-
client: createServerApiClient(apiUrl,
|
|
395
|
+
client: createServerApiClient(apiUrl, localAuth.token, generateUuidv7()),
|
|
384
396
|
projectUuid,
|
|
385
397
|
logger: server.config.logger
|
|
386
398
|
};
|
|
387
|
-
|
|
388
|
-
if (afterSync) try {
|
|
399
|
+
try {
|
|
389
400
|
await afterSync(context);
|
|
390
401
|
} catch (error) {
|
|
391
402
|
context.logger.warn(`[camox] afterSync failed: ${String(error)}`, { timestamp: true });
|
|
392
403
|
}
|
|
393
|
-
if (releasedToLive) await publishSeededContent(context);
|
|
394
404
|
});
|
|
395
405
|
});
|
|
396
406
|
},
|
|
@@ -414,7 +424,6 @@ function camox(options) {
|
|
|
414
424
|
definitionSet: await camoxModule.camoxApp.getDefinitionSet(),
|
|
415
425
|
projectUuid,
|
|
416
426
|
apiUrl,
|
|
417
|
-
autoCreate: false,
|
|
418
427
|
deployToken,
|
|
419
428
|
logger: resolvedConfig.logger
|
|
420
429
|
});
|