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.
Files changed (79) hide show
  1. package/dist/core/createApp.d.ts +2 -2
  2. package/dist/core/createBlock.js +1 -1
  3. package/dist/core/createLayout.d.ts +1 -1
  4. package/dist/core/editing/createEditableBlock.d.ts +1 -1
  5. package/dist/core/editing/createEditableBlock.js +1 -1
  6. package/dist/core/lib/recordScope.js +1 -1
  7. package/dist/features/canvas/CamoxCanvas.js +2 -2
  8. package/dist/features/canvas/CanvasOverlays.js +1 -1
  9. package/dist/features/canvas/sidebar/LayoutPageSeo.js +1 -1
  10. package/dist/features/canvas/sidebar/PageInspector.js +1 -1
  11. package/dist/features/content/CamoxContent.js +1 -1
  12. package/dist/features/content/components/MissingFromLocale.js +1 -1
  13. package/dist/features/page/CuratedPageContent.js +1 -1
  14. package/dist/features/preview/CamoxPreview.js +3 -3
  15. package/dist/features/preview/components/AddBlockSheet.js +1 -1
  16. package/dist/features/preview/components/AssetLightbox.js +1 -1
  17. package/dist/features/preview/components/BlockActionsPopover.js +1 -1
  18. package/dist/features/preview/components/CreatePageModal.js +2 -2
  19. package/dist/features/preview/components/FieldOverlayStyles.js +1 -1
  20. package/dist/features/preview/components/MobilePreviewDrawer.js +1 -1
  21. package/dist/features/preview/components/Overlays.js +1 -1
  22. package/dist/features/preview/components/PageInfo.js +1 -1
  23. package/dist/features/preview/components/PagePicker.js +1 -1
  24. package/dist/features/preview/components/PageTree.js +1 -1
  25. package/dist/features/preview/components/PreviewPanel.js +1 -1
  26. package/dist/features/preview/components/PreviewToolbar.js +1 -1
  27. package/dist/features/preview/components/SelectionEditor.js +1 -1
  28. package/dist/features/preview/components/StudioEditingFallback.js +1 -1
  29. package/dist/features/provider/AuthenticatedCamoxProvider.js +1 -1
  30. package/dist/features/provider/CamoxProvider.js +1 -1
  31. package/dist/features/provider/CoreCamoxProvider.js +3 -3
  32. package/dist/features/provider/components/CommandPalette.js +1 -1
  33. package/dist/features/renderer/rendererHost.js +3 -7
  34. package/dist/features/routes/pageRuntime.d.ts +2 -5
  35. package/dist/features/routes/pageRuntime.js +1 -2
  36. package/dist/features/runtime/RuntimeChrome.js +1 -1
  37. package/dist/features/runtime/navigationTarget.js +1 -2
  38. package/dist/features/runtime/pageApp.js +1 -1
  39. package/dist/features/runtime/rendererClient.js +1 -1
  40. package/dist/features/runtime/runtime.d.ts +0 -5
  41. package/dist/features/runtime/runtime.js +28 -32
  42. package/dist/features/runtime/worker.js +3 -3
  43. package/dist/features/settings/FaviconSettings.js +1 -1
  44. package/dist/features/studio/CamoxStudio.js +1 -1
  45. package/dist/features/studio/FollowAgentProvider.js +1 -1
  46. package/dist/features/studio/components/ChatWidget.js +1 -1
  47. package/dist/features/studio/components/DraftsControl.js +232 -201
  48. package/dist/features/studio/components/LocalePicker.js +2 -2
  49. package/dist/features/studio/components/Navbar.js +3 -3
  50. package/dist/features/studio/components/Patrol.js +1 -1
  51. package/dist/features/studio/components/ProjectMenu.js +2 -2
  52. package/dist/features/studio/components/ReviewDraftSheet.js +9 -15
  53. package/dist/features/studio/components/ThreadMessages.js +103 -21
  54. package/dist/features/studio/components/UserButton.js +2 -2
  55. package/dist/features/studio/draftLabel.js +1 -2
  56. package/dist/features/studio/draftNotice.js +0 -1
  57. package/dist/features/studio/showInCanvas.js +1 -1
  58. package/dist/features/vite/definitionsSync.js +14 -60
  59. package/dist/features/vite/devSignIn.js +1 -1
  60. package/dist/features/vite/runtimeDev.js +0 -1
  61. package/dist/features/vite/vite.d.ts +8 -5
  62. package/dist/features/vite/vite.js +34 -25
  63. package/dist/lib/api-client-server.js +25 -8
  64. package/dist/lib/api-client.js +2 -6
  65. package/dist/lib/auth.js +1 -1
  66. package/dist/lib/draft-scope.js +5 -25
  67. package/dist/lib/preview-handoff.js +1 -1
  68. package/dist/lib/server-auth-cookie.js +12 -10
  69. package/dist/lib/studio-reads.js +1 -1
  70. package/dist/lib/use-thread.js +31 -35
  71. package/dist/package.js +1 -1
  72. package/dist/studio.css +1 -1
  73. package/package.json +4 -4
  74. package/skills/camox/references/block-definitions.md +1 -1
  75. package/dist/features/vite/publishSeededContent.js +0 -30
  76. package/skills/camox-cli/SKILL.md +0 -15
  77. package/skills/camox-cli/references/cli-common.md +0 -58
  78. package/skills/camox-cli/references/cli-content.md +0 -181
  79. 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 development draft can hold dozens of definitions to release, and a removed
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 agent changed the code since this review opened, so the publish stopped. Check the preview, then publish again.",
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
- if (draftScope.leave(ended) && ended) toast.success(`Draft ${ended.outcome}. You're now viewing Live.`);
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?.kind === "regular" ? /* @__PURE__ */ jsx(DraftNameInput, {
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?.kind === "regular" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SheetTitle, {
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: draft.data ? draftLabel(draft.data) : "Draft"
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(17);
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 ($[7] === Symbol.for("react.memo_cache_sentinel")) {
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
- $[7] = t1;
264
- } else t1 = $[7];
339
+ $[26] = t1;
340
+ } else t1 = $[26];
265
341
  let t2;
266
- if ($[8] !== message.text) {
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
- $[8] = message.text;
276
- $[9] = t2;
277
- } else t2 = $[9];
351
+ $[27] = message.text;
352
+ $[28] = t2;
353
+ } else t2 = $[28];
278
354
  return t2;
279
355
  }
280
356
  let t1;
281
- if ($[10] !== message.sender) {
357
+ if ($[29] !== message.sender) {
282
358
  t1 = /* @__PURE__ */ jsx(MessageAvatar, { children: /* @__PURE__ */ jsx(SenderAvatar, { sender: message.sender }) });
283
- $[10] = message.sender;
284
- $[11] = t1;
285
- } else t1 = $[11];
359
+ $[29] = message.sender;
360
+ $[30] = t1;
361
+ } else t1 = $[30];
286
362
  let t2;
287
- if ($[12] !== message.text) {
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
- $[12] = message.text;
298
- $[13] = t2;
299
- } else t2 = $[13];
373
+ $[31] = message.text;
374
+ $[32] = t2;
375
+ } else t2 = $[32];
300
376
  let t3;
301
- if ($[14] !== t1 || $[15] !== t2) {
377
+ if ($[33] !== t1 || $[34] !== t2) {
302
378
  t3 = /* @__PURE__ */ jsxs(Message, {
303
379
  align: "end",
304
380
  children: [t1, t2]
305
381
  });
306
- $[14] = t1;
307
- $[15] = t2;
308
- $[16] = t3;
309
- } else t3 = $[16];
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". A development draft is the development draft.
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
- * Send a Definition Set to the API: Live's for a release, the development draft's in dev mode.
40
- * It goes in one `definitions.sync` call, which either fully applies it or changes nothing.
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, autoCreate, deployToken, logger } = options;
44
- const client = createSyncClient(options);
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 (!autoCreate && isNotFoundError(error)) throwSyncTargetNotFoundError(projectUuid, deployToken);
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
- * Sync the app's whole Definition Set to the development draft now, then again whenever a module
140
- * the app depends on changes (debounced), skipping a sync when the set equals the last one sent
141
- * (ADR 0006). Resolves once the initial sync settles: `synced` if definitions reached the API,
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 the development draft last received, so an unchanged set isn't sent again. */
121
+ /** What Live last received, so an unchanged set isn't sent again. */
148
122
  let lastSynced;
149
- let releasedToLive = false;
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({ initial: true });
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 run camox preview for a fresh link.");
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
- /** Sends the dev session and reads and writes the developer's development draft. */
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: a deploy token for seeding Live. When Live has no pages yet, the first sync
49
- * releases the local definitions to Live, and the development draft is published after
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
- seedLiveDeployToken?: string;
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. No draft is written here: the CLI always works in the
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 (!isRelease) {
268
- localAuth = readAuthTokenForUrl(authenticationUrl);
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: { emitAssets: true },
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 = "your development draft";
358
+ let target = "Live, not released";
346
359
  if (isDryRun) target = "Live, dry run";
347
360
  else if (isRelease) target = "Live";
348
- else if (!localAuth) target = "authentication required";
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 (!localAuth) return;
373
- const authToken = localAuth.token;
386
+ if (!localDeployToken) return;
374
387
  syncDefinitions(server, {
375
388
  projectUuid,
376
389
  apiUrl,
377
- autoCreate: true,
378
- authToken,
379
- releaseToEmptyLive: options._internal?.seedLiveDeployToken
380
- }).then(async ({ synced, releasedToLive }) => {
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, authToken, DEVELOPMENT_DRAFT),
395
+ client: createServerApiClient(apiUrl, localAuth.token, generateUuidv7()),
384
396
  projectUuid,
385
397
  logger: server.config.logger
386
398
  };
387
- const afterSync = options._internal?.afterSync;
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
  });