camox 0.76.1 → 0.78.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 (33) hide show
  1. package/dist/core/lib/artifactTheme.js +43 -6
  2. package/dist/features/canvas/CamoxCanvas.js +187 -197
  3. package/dist/features/canvas/canvasCameraController.js +1 -1
  4. package/dist/features/canvas/canvasItems.js +40 -9
  5. package/dist/features/canvas/sidebar/ContentSidebarFrame.js +15 -11
  6. package/dist/features/canvas/useCanvasLayout.js +34 -0
  7. package/dist/features/memory/CamoxMemory.js +747 -0
  8. package/dist/features/preview/EditablePageExperience.js +2 -0
  9. package/dist/features/runtime/publicCache.d.ts +2 -0
  10. package/dist/features/runtime/publicCache.js +27 -4
  11. package/dist/features/runtime/runtime.d.ts +1 -1
  12. package/dist/features/runtime/runtime.js +34 -15
  13. package/dist/features/studio/FollowAgentProvider.js +1 -1
  14. package/dist/features/studio/components/AgentSidebar.js +22 -16
  15. package/dist/features/studio/components/DraftBar.js +167 -103
  16. package/dist/features/studio/components/{ReviewDraftDialog.js → DraftReview.js} +191 -218
  17. package/dist/features/studio/components/DraftsControl.js +174 -202
  18. package/dist/features/studio/components/DraftsSheet.js +102 -94
  19. package/dist/features/studio/components/LocalePicker.js +518 -134
  20. package/dist/features/studio/components/Navbar.js +121 -84
  21. package/dist/features/studio/components/PublishedTab.js +21 -20
  22. package/dist/features/studio/routes.js +3 -1
  23. package/dist/features/studio/studioStore.js +12 -9
  24. package/dist/features/studio/usePublishDraft.js +116 -0
  25. package/dist/features/studio/viewSwitch.js +10 -7
  26. package/dist/lib/locale-path.js +2 -2
  27. package/dist/lib/queries.js +12 -2
  28. package/dist/lib/studio-reads.js +6 -0
  29. package/dist/package.js +1 -1
  30. package/dist/studio.css +1 -1
  31. package/package.json +5 -5
  32. package/skills/camox/references/artifacts.md +7 -0
  33. package/LICENSE.md +0 -21
@@ -4,7 +4,7 @@ import { actionsStore } from "../../provider/actionsStore.js";
4
4
  import { cn } from "../../../lib/utils.js";
5
5
  import { usePageDestinations } from "../../../hooks/use-page-destinations.js";
6
6
  import { useLocalePath, useLocalizedPath } from "../../../lib/view-locale.js";
7
- import { STUDIO_BASE_PATH, STUDIO_CONTENT_PATH, STUDIO_SETTINGS_PATH } from "../routes.js";
7
+ import { STUDIO_BASE_PATH, STUDIO_CONTENT_PATH, STUDIO_MEMORY_PATH, STUDIO_SETTINGS_PATH } from "../routes.js";
8
8
  import { studioStore } from "../studioStore.js";
9
9
  import { UserButton } from "./UserButton.js";
10
10
  import { ConnectAgent } from "./ConnectAgent.js";
@@ -16,28 +16,43 @@ import * as React from "react";
16
16
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
17
17
  import { useSelector } from "@xstate/store-react";
18
18
  import { buttonVariants } from "@camox/ui/button";
19
- import { Database, Globe } from "lucide-react";
19
+ import { Brain, Database, Globe } from "lucide-react";
20
20
 
21
21
  //#region src/features/studio/components/Navbar.tsx
22
- const links = [{
23
- to: "/",
24
- title: "Website",
25
- isWebsite: true,
26
- children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Globe, {}), "Website"] }),
27
- icon: "Globe",
28
- aliases: ["Website"]
29
- }, {
30
- to: STUDIO_CONTENT_PATH,
31
- title: "Content",
32
- children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Database, {}), "Content"] }),
33
- icon: "FileText",
34
- aliases: [
35
- "Content",
36
- "Pages",
37
- "CMS"
38
- ]
39
- }];
22
+ const links = [
23
+ {
24
+ to: "/",
25
+ title: "Website",
26
+ isWebsite: true,
27
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Globe, {}), "Website"] }),
28
+ icon: "Globe",
29
+ aliases: ["Website"]
30
+ },
31
+ {
32
+ to: STUDIO_MEMORY_PATH,
33
+ title: "Memory",
34
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Brain, {}), "Memory"] }),
35
+ icon: "Brain",
36
+ aliases: [
37
+ "Memory",
38
+ "Memories",
39
+ "Remember"
40
+ ]
41
+ },
42
+ {
43
+ to: STUDIO_CONTENT_PATH,
44
+ title: "Content",
45
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Database, {}), "Content"] }),
46
+ icon: "FileText",
47
+ aliases: [
48
+ "Content",
49
+ "Pages",
50
+ "CMS"
51
+ ]
52
+ }
53
+ ];
40
54
  const getNavbarLinks = () => links;
55
+ const WEBSITE_LINK = links.find((link) => link.isWebsite);
41
56
  /**
42
57
  * Where a navbar link goes. The Website goes back where it was last, Canvas included. In another
43
58
  * locale since, a Studio location stays, while a page, whose path may differ there, goes home.
@@ -48,11 +63,25 @@ function navbarHref(link, last, locale, localized) {
48
63
  if (last.path.startsWith(`${"/camox"}/`)) return localized(last.path);
49
64
  return localized(link.to);
50
65
  }
66
+ /** Where the Website link goes now: where the website was last, Canvas included. */
67
+ function useWebsiteHref() {
68
+ const $ = c(3);
69
+ const localized = useLocalizedPath();
70
+ const { locale } = useViewLocale();
71
+ let t0;
72
+ if ($[0] !== locale.tag || $[1] !== localized) {
73
+ t0 = () => navbarHref(WEBSITE_LINK, studioStore.getSnapshot().context.websiteLocation, locale.tag, localized);
74
+ $[0] = locale.tag;
75
+ $[1] = localized;
76
+ $[2] = t0;
77
+ } else t0 = $[2];
78
+ return t0;
79
+ }
51
80
  /** Whether `pathname` is in the studio section at `sectionPath`. */
52
81
  const isInSection = (sectionPath, pathname) => pathname === sectionPath || pathname.startsWith(`${sectionPath}/`);
53
82
  const isSettingsNavbarPath = (pathname) => isInSection(STUDIO_SETTINGS_PATH, pathname);
54
83
  const Navbar = (t0) => {
55
- const $ = c(26);
84
+ const $ = c(28);
56
85
  const { isWebsite: t1, recordPage } = t0;
57
86
  const isWebsite = t1 === void 0 ? false : t1;
58
87
  const pages = usePageDestinations();
@@ -68,43 +97,51 @@ const Navbar = (t0) => {
68
97
  } else t2 = $[1];
69
98
  const isSettings = t2;
70
99
  let t3;
71
- if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
72
- t3 = /* @__PURE__ */ jsx(ProjectMenu, {});
73
- $[2] = t3;
74
- } else t3 = $[2];
100
+ if ($[2] !== pathname) {
101
+ t3 = isInSection(STUDIO_MEMORY_PATH, pathname);
102
+ $[2] = pathname;
103
+ $[3] = t3;
104
+ } else t3 = $[3];
105
+ const isMemory = t3;
75
106
  let t4;
76
- if ($[3] !== isSettings) {
77
- t4 = /* @__PURE__ */ jsx(DraftPicker, { disabled: isSettings });
78
- $[3] = isSettings;
107
+ if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
108
+ t4 = /* @__PURE__ */ jsx(ProjectMenu, {});
79
109
  $[4] = t4;
80
110
  } else t4 = $[4];
81
111
  let t5;
82
- if ($[5] !== isSettings || $[6] !== recordPage) {
83
- t5 = /* @__PURE__ */ jsx(LocalePicker, {
112
+ if ($[5] !== isSettings) {
113
+ t5 = /* @__PURE__ */ jsx(DraftPicker, { disabled: isSettings });
114
+ $[5] = isSettings;
115
+ $[6] = t5;
116
+ } else t5 = $[6];
117
+ const t6 = isSettings || isMemory;
118
+ let t7;
119
+ if ($[7] !== recordPage || $[8] !== t6) {
120
+ t7 = /* @__PURE__ */ jsx(LocalePicker, {
84
121
  recordPage,
85
- disabled: isSettings
122
+ disabled: t6
86
123
  });
87
- $[5] = isSettings;
88
- $[6] = recordPage;
89
- $[7] = t5;
90
- } else t5 = $[7];
91
- let t6;
92
- if ($[8] !== t4 || $[9] !== t5) {
93
- t6 = /* @__PURE__ */ jsxs("div", {
124
+ $[7] = recordPage;
125
+ $[8] = t6;
126
+ $[9] = t7;
127
+ } else t7 = $[9];
128
+ let t8;
129
+ if ($[10] !== t5 || $[11] !== t7) {
130
+ t8 = /* @__PURE__ */ jsxs("div", {
94
131
  className: "flex items-center gap-1",
95
132
  children: [
96
- t3,
97
133
  t4,
98
- t5
134
+ t5,
135
+ t7
99
136
  ]
100
137
  });
101
- $[8] = t4;
102
- $[9] = t5;
103
- $[10] = t6;
104
- } else t6 = $[10];
105
- let t7;
106
- if ($[11] !== isWebsite || $[12] !== last || $[13] !== locale || $[14] !== localized || $[15] !== pages || $[16] !== pathname) {
107
- t7 = getNavbarLinks().map((link) => {
138
+ $[10] = t5;
139
+ $[11] = t7;
140
+ $[12] = t8;
141
+ } else t8 = $[12];
142
+ let t9;
143
+ if ($[13] !== isWebsite || $[14] !== last || $[15] !== locale || $[16] !== localized || $[17] !== pages || $[18] !== pathname) {
144
+ t9 = getNavbarLinks().map((link) => {
108
145
  const isActive = link.isWebsite ? isWebsite || !!pages?.some((page) => page.fullPath === pathname) : isInSection(link.to, pathname);
109
146
  return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link$1, {
110
147
  to: navbarHref(link, last, locale.tag, localized),
@@ -114,51 +151,51 @@ const Navbar = (t0) => {
114
151
  children: link.children
115
152
  }) }, link.to);
116
153
  });
117
- $[11] = isWebsite;
118
- $[12] = last;
119
- $[13] = locale;
120
- $[14] = localized;
121
- $[15] = pages;
122
- $[16] = pathname;
123
- $[17] = t7;
124
- } else t7 = $[17];
125
- let t8;
126
- if ($[18] !== t7) {
127
- t8 = /* @__PURE__ */ jsx("ul", {
154
+ $[13] = isWebsite;
155
+ $[14] = last;
156
+ $[15] = locale;
157
+ $[16] = localized;
158
+ $[17] = pages;
159
+ $[18] = pathname;
160
+ $[19] = t9;
161
+ } else t9 = $[19];
162
+ let t10;
163
+ if ($[20] !== t9) {
164
+ t10 = /* @__PURE__ */ jsx("ul", {
128
165
  className: "flex items-center gap-1",
129
- children: t7
166
+ children: t9
130
167
  });
131
- $[18] = t7;
132
- $[19] = t8;
133
- } else t8 = $[19];
134
- let t9;
135
- if ($[20] !== t6 || $[21] !== t8) {
136
- t9 = /* @__PURE__ */ jsxs("div", {
168
+ $[20] = t9;
169
+ $[21] = t10;
170
+ } else t10 = $[21];
171
+ let t11;
172
+ if ($[22] !== t10 || $[23] !== t8) {
173
+ t11 = /* @__PURE__ */ jsxs("div", {
137
174
  className: "flex flex-row gap-2",
138
- children: [t6, t8]
175
+ children: [t8, t10]
139
176
  });
140
- $[20] = t6;
141
- $[21] = t8;
142
- $[22] = t9;
143
- } else t9 = $[22];
144
- let t10;
145
- if ($[23] === Symbol.for("react.memo_cache_sentinel")) {
146
- t10 = /* @__PURE__ */ jsxs("div", {
177
+ $[22] = t10;
178
+ $[23] = t8;
179
+ $[24] = t11;
180
+ } else t11 = $[24];
181
+ let t12;
182
+ if ($[25] === Symbol.for("react.memo_cache_sentinel")) {
183
+ t12 = /* @__PURE__ */ jsxs("div", {
147
184
  className: "flex items-center gap-2",
148
185
  children: [/* @__PURE__ */ jsx(ConnectAgent, {}), /* @__PURE__ */ jsx(UserButton, {})]
149
186
  });
150
- $[23] = t10;
151
- } else t10 = $[23];
152
- let t11;
153
- if ($[24] !== t9) {
154
- t11 = /* @__PURE__ */ jsxs("nav", {
187
+ $[25] = t12;
188
+ } else t12 = $[25];
189
+ let t13;
190
+ if ($[26] !== t11) {
191
+ t13 = /* @__PURE__ */ jsxs("nav", {
155
192
  className: "bg-background relative flex items-center justify-between gap-4 border-b px-2 py-2",
156
- children: [t9, t10]
193
+ children: [t11, t12]
157
194
  });
158
- $[24] = t9;
159
- $[25] = t11;
160
- } else t11 = $[25];
161
- return t11;
195
+ $[26] = t11;
196
+ $[27] = t13;
197
+ } else t13 = $[27];
198
+ return t13;
162
199
  };
163
200
  function useNavbarActions() {
164
201
  const $ = c(5);
@@ -240,4 +277,4 @@ function _temp(state) {
240
277
  }
241
278
 
242
279
  //#endregion
243
- export { Navbar, useNavbarActions };
280
+ export { Navbar, useNavbarActions, useWebsiteHref };
@@ -17,45 +17,46 @@ import { Spinner } from "@camox/ui/spinner";
17
17
 
18
18
  //#region src/features/studio/components/PublishedTab.tsx
19
19
  /**
20
- * The Publications (ADR 0045), the latest first. Opening one shows its overview and its thread,
21
- * read-only: how a member finds out why Live changed. Reverting one takes the member into the
22
- * draft it starts, once `onEntered` closed the sheet.
20
+ * The Publications (ADR 0045), the latest first, or the one `opening` names. Opening one shows its
21
+ * overview and its thread, read-only: how a member finds out why Live changed. Reverting one takes
22
+ * the member into the draft it starts, once `onEntered` closed the sheet.
23
23
  */
24
24
  function PublishedTab(t0) {
25
25
  const $ = c(7);
26
- const { projectId, onEntered } = t0;
27
- const [opened, setOpened] = React.useState(null);
26
+ const { projectId, opening: t1, onEntered } = t0;
27
+ const opening = t1 === void 0 ? null : t1;
28
+ const [opened, setOpened] = React.useState(opening);
28
29
  if (opened) {
29
- let t1;
30
- if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
31
- t1 = () => setOpened(null);
32
- $[0] = t1;
33
- } else t1 = $[0];
34
30
  let t2;
31
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
32
+ t2 = () => setOpened(null);
33
+ $[0] = t2;
34
+ } else t2 = $[0];
35
+ let t3;
35
36
  if ($[1] !== onEntered || $[2] !== opened || $[3] !== projectId) {
36
- t2 = /* @__PURE__ */ jsx(PublicationView, {
37
+ t3 = /* @__PURE__ */ jsx(PublicationView, {
37
38
  projectId,
38
39
  id: opened,
39
- onBack: t1,
40
+ onBack: t2,
40
41
  onEntered
41
42
  });
42
43
  $[1] = onEntered;
43
44
  $[2] = opened;
44
45
  $[3] = projectId;
45
- $[4] = t2;
46
- } else t2 = $[4];
47
- return t2;
46
+ $[4] = t3;
47
+ } else t3 = $[4];
48
+ return t3;
48
49
  }
49
- let t1;
50
+ let t2;
50
51
  if ($[5] !== projectId) {
51
- t1 = /* @__PURE__ */ jsx(PublicationList, {
52
+ t2 = /* @__PURE__ */ jsx(PublicationList, {
52
53
  projectId,
53
54
  onOpen: setOpened
54
55
  });
55
56
  $[5] = projectId;
56
- $[6] = t1;
57
- } else t1 = $[6];
58
- return t1;
57
+ $[6] = t2;
58
+ } else t2 = $[6];
59
+ return t2;
59
60
  }
60
61
  function PublicationList(t0) {
61
62
  const $ = c(16);
@@ -3,6 +3,8 @@ const STUDIO_ROUTE_SEGMENT = "camox";
3
3
  const STUDIO_BASE_PATH = `/${STUDIO_ROUTE_SEGMENT}`;
4
4
  const STUDIO_CONTENT_PATH = `${STUDIO_BASE_PATH}/content`;
5
5
  const STUDIO_ASSETS_PATH = `${STUDIO_CONTENT_PATH}/assets`;
6
+ /** The Memory Section, where members see and correct what Camox's agent remembers. */
7
+ const STUDIO_MEMORY_PATH = `${STUDIO_BASE_PATH}/memory`;
6
8
  /** The Settings Section, where members change Project Settings. */
7
9
  const STUDIO_SETTINGS_PATH = `${STUDIO_BASE_PATH}/settings`;
8
10
  const collectionContentPath = (collectionId) => `${STUDIO_CONTENT_PATH}/collections/${encodeURIComponent(collectionId)}`;
@@ -32,4 +34,4 @@ function matchCollectionContentPath(pathname) {
32
34
  const STUDIO_CANVAS_PATH = `${STUDIO_BASE_PATH}/canvas`;
33
35
 
34
36
  //#endregion
35
- export { STUDIO_ASSETS_PATH, STUDIO_BASE_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, STUDIO_ROUTE_SEGMENT, STUDIO_SETTINGS_PATH, collectionContentPath, editCollectionItemPath, matchCollectionContentPath };
37
+ export { STUDIO_ASSETS_PATH, STUDIO_BASE_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, STUDIO_MEMORY_PATH, STUDIO_ROUTE_SEGMENT, STUDIO_SETTINGS_PATH, collectionContentPath, editCollectionItemPath, matchCollectionContentPath };
@@ -5,8 +5,9 @@ const studioStore = createStore({
5
5
  context: {
6
6
  isCommandPaletteOpen: false,
7
7
  commandPalettePages: [],
8
- isReviewDraftDialogOpen: false,
8
+ isDraftReviewOpen: false,
9
9
  isDraftsSheetOpen: false,
10
+ draftsSheetPublication: null,
10
11
  localeDialog: null,
11
12
  websiteLocation: null,
12
13
  agentMessages: [],
@@ -46,28 +47,30 @@ const studioStore = createStore({
46
47
  commandPalettePages: context.commandPalettePages.slice(0, -1)
47
48
  };
48
49
  },
49
- openReviewDraftDialog: (context) => {
50
+ openDraftReview: (context) => {
50
51
  return {
51
52
  ...context,
52
- isReviewDraftDialogOpen: true
53
+ isDraftReviewOpen: true
53
54
  };
54
55
  },
55
- closeReviewDraftDialog: (context) => {
56
+ closeDraftReview: (context) => {
56
57
  return {
57
58
  ...context,
58
- isReviewDraftDialogOpen: false
59
+ isDraftReviewOpen: false
59
60
  };
60
61
  },
61
- openDraftsSheet: (context) => {
62
+ openDraftsSheet: (context, event) => {
62
63
  return {
63
64
  ...context,
64
- isDraftsSheetOpen: true
65
+ isDraftsSheetOpen: true,
66
+ draftsSheetPublication: event.publicationId ?? null
65
67
  };
66
68
  },
67
69
  closeDraftsSheet: (context) => {
68
70
  return {
69
71
  ...context,
70
- isDraftsSheetOpen: false
72
+ isDraftsSheetOpen: false,
73
+ draftsSheetPublication: null
71
74
  };
72
75
  },
73
76
  openLocaleDialog: (context, event) => {
@@ -86,7 +89,7 @@ const studioStore = createStore({
86
89
  const id = context.agentMessagesSent + 1;
87
90
  return {
88
91
  ...context,
89
- isReviewDraftDialogOpen: false,
92
+ isDraftReviewOpen: false,
90
93
  agentMessages: [...context.agentMessages, {
91
94
  text: event.text,
92
95
  id
@@ -0,0 +1,116 @@
1
+ import { reads, useReadQuery, useStudioCache } from "../../lib/studio-reads.js";
2
+ import { draftMutations } from "../../lib/queries.js";
3
+ import { publishToast, useLeaveDraft } from "./viewSwitch.js";
4
+ import * as React from "react";
5
+ import { PUBLISH_BLOCKED_ERROR } from "@camox/api-contract/drafts";
6
+ import { useMutation } from "@tanstack/react-query";
7
+ import { toast } from "@camox/ui/toaster";
8
+
9
+ //#region src/features/studio/usePublishDraft.ts
10
+ const PUBLISH_FAILED = "The publish couldn't finish on Camox's side, and Live is as it was. Try again.";
11
+ const PUBLISH_BLOCKED = "This draft has blockers to fix before it can publish.";
12
+ /**
13
+ * Why the last publish with code stopped, when it isn't a blocker, which the review shows
14
+ * instead.
15
+ */
16
+ const PUBLISH_STOPPED = {
17
+ revisionChanged: "The draft's code changed while publishing, so the publish stopped and Live is as it was. Publish again.",
18
+ liveCodeMoved: "Live's code changed while publishing, and the draft took too long to take it in, so the publish stopped and Live is as it was. Try again.",
19
+ conflictsChanged: PUBLISH_FAILED,
20
+ failed: PUBLISH_FAILED
21
+ };
22
+ /**
23
+ * Whether a publish request failed on Camox's side rather than being refused: a server error,
24
+ * or no answer at all. Either way the publish applied nothing.
25
+ */
26
+ const failedOnCamoxSide = (error) => {
27
+ const status = error.status;
28
+ return typeof status !== "number" || status >= 500;
29
+ };
30
+ /** What Publish sends: the revision a draft with code shows, which publishing ships. */
31
+ const publishInput = (projectId, shown) => ({
32
+ projectId,
33
+ ...shown.code?.revision && { revision: shown.code.revision }
34
+ });
35
+ /**
36
+ * Publishes the draft the studio views, at what its review reads now (ADR 0043). One toast
37
+ * follows the publish: it says it's publishing, then turns into how it ended. Once the draft
38
+ * ends, the studio switches to Live, and the toast says so. A draft with code publishes in the
39
+ * background: `publishing` stays true until it lands or stops. Blockers, found before publishing
40
+ * or stopping it, call `onBlocked`, for the review to show them.
41
+ *
42
+ * The review is read only to publish, and followed while a background publish runs: reading it
43
+ * hurries the draft's overview.
44
+ */
45
+ function usePublishDraft({ projectId, draftId, onBlocked }) {
46
+ const read = reads.publishPreview({
47
+ projectId,
48
+ draftId
49
+ });
50
+ const cache = useStudioCache();
51
+ const leaveDraft = useLeaveDraft();
52
+ const id = publishToast(draftId);
53
+ const [following, setFollowing] = React.useState(false);
54
+ const preview = useReadQuery(read, { enabled: following });
55
+ const blocked = () => {
56
+ toast.error(PUBLISH_BLOCKED, { id });
57
+ onBlocked?.();
58
+ };
59
+ const publish = useMutation({
60
+ ...draftMutations.publish(),
61
+ onSuccess: async (result) => {
62
+ if ("publishing" in result && result.publishing) {
63
+ setFollowing(true);
64
+ await cache.reload(read).catch(() => {});
65
+ return;
66
+ }
67
+ if (!result.empty) return leaveDraft(result.ended);
68
+ toast.info("This draft has no changes, so there is nothing to publish.", { id });
69
+ cache.reload(read).catch(() => {});
70
+ },
71
+ onError: (error) => {
72
+ cache.reload(read).catch(() => {});
73
+ if (failedOnCamoxSide(error)) toast.error(PUBLISH_FAILED, { id });
74
+ else if (error.code === PUBLISH_BLOCKED_ERROR) blocked();
75
+ else toast.error(error.message, { id });
76
+ }
77
+ });
78
+ const [reading, setReading] = React.useState(false);
79
+ const start = async () => {
80
+ toast.loading("Publishing…", { id });
81
+ setReading(true);
82
+ let shown;
83
+ try {
84
+ shown = await cache.reload(read);
85
+ } catch {
86
+ toast.error(PUBLISH_FAILED, { id });
87
+ return;
88
+ } finally {
89
+ setReading(false);
90
+ }
91
+ if (shown.blockers.length > 0) return blocked();
92
+ publish.mutate(publishInput(projectId, shown));
93
+ };
94
+ const data = preview.data;
95
+ const backgroundPublish = !!data?.code?.publishing;
96
+ const [stopped, setStopped] = React.useState(null);
97
+ if (following && !publish.isPending && data && !backgroundPublish) {
98
+ setFollowing(false);
99
+ setStopped(data);
100
+ }
101
+ const reportStop = React.useEffectEvent((shown) => {
102
+ const reason = shown.code?.publishAborted;
103
+ if (shown.blockers.length > 0) return blocked();
104
+ toast.error((reason && PUBLISH_STOPPED[reason]) ?? "The publish couldn't finish on Camox's side, and Live is as it was. Try again.", { id });
105
+ });
106
+ React.useEffect(() => {
107
+ if (stopped) reportStop(stopped);
108
+ }, [stopped]);
109
+ return {
110
+ publish: start,
111
+ publishing: reading || publish.isPending || backgroundPublish
112
+ };
113
+ }
114
+
115
+ //#endregion
116
+ export { PUBLISH_FAILED, PUBLISH_STOPPED, usePublishDraft };
@@ -32,9 +32,14 @@ function useSwitchView() {
32
32
  return t0;
33
33
  }
34
34
  /**
35
+ * The toast of a draft's publish, from Publish to the studio showing Live: each toast with this
36
+ * id replaces the last.
37
+ */
38
+ const publishToast = (draftId) => `publish:${draftId}`;
39
+ /**
35
40
  * Switches the studio to Live once the draft it views ended or was archived, by this member, here
36
- * or in another tab, replacing the draft's URL entry. An ended draft's dialog already showed it
37
- * working, so its toast only says so once Live shows; an archived draft's toast shows meanwhile
41
+ * or in another tab, replacing the draft's URL entry. An ended draft's publish toast already
42
+ * showed it working, so it only turns into saying so once Live shows; an archived draft's toast shows meanwhile
38
43
  * too. Called once the studio already left the draft, it does nothing.
39
44
  */
40
45
  function useLeaveDraft() {
@@ -48,7 +53,8 @@ function useLeaveDraft() {
48
53
  draftScope.leave(left);
49
54
  if (!left) return;
50
55
  if ("outcome" in left) {
51
- shown().then(() => toast.success(`Draft ${left.outcome}. You're now viewing Live.`), _temp);
56
+ const id = publishToast(left.draftId);
57
+ shown().then(() => toast.success(`Draft ${left.outcome}. You're now viewing Live.`, { id }), () => toast.error(SWITCH_FAILED, { id }));
52
58
  return;
53
59
  }
54
60
  toastSwitch(shown(), {
@@ -62,9 +68,6 @@ function useLeaveDraft() {
62
68
  } else t0 = $[2];
63
69
  return t0;
64
70
  }
65
- function _temp() {
66
- return toast.error(SWITCH_FAILED);
67
- }
68
71
 
69
72
  //#endregion
70
- export { useLeaveDraft, useSwitchView };
73
+ export { publishToast, useLeaveDraft, useSwitchView };
@@ -1,5 +1,5 @@
1
1
  import { LOCALE_HEADER } from "@camox/api-contract/drafts";
2
- import { localizedPath, parseLocalePath } from "@camox/api-contract/locales";
2
+ import { localePrefix, localizedPath, parseLocalePath } from "@camox/api-contract/locales";
3
3
 
4
4
  //#region src/lib/locale-path.ts
5
5
  /**
@@ -24,4 +24,4 @@ function localeHeaders(locale) {
24
24
  }
25
25
 
26
26
  //#endregion
27
- export { UNNAMED_DEFAULT_LOCALE, localeHeaders, localizedPath, parseLocalePath };
27
+ export { UNNAMED_DEFAULT_LOCALE, localeHeaders, localePrefix, localizedPath, parseLocalePath };
@@ -69,13 +69,23 @@ const specialistMutations = {
69
69
  applyFix: () => getOrpc().specialists.applyFix.mutationOptions(),
70
70
  dismissFinding: () => getOrpc().specialists.dismissFinding.mutationOptions()
71
71
  };
72
- /** Adding and removing locales: content writes, so on Live they start a draft (ADR 0015). */
72
+ /**
73
+ * Adding and removing locales, and making one the default: content writes, so on Live they start
74
+ * a draft (ADR 0015).
75
+ */
73
76
  const localeMutations = {
74
77
  add: () => getOrpc().locales.add.mutationOptions(),
75
78
  remove: () => getOrpc().locales.remove.mutationOptions(),
79
+ setDefault: () => getOrpc().locales.setDefault.mutationOptions(),
76
80
  /** Adds a page or record deleted from the locale viewed back to it. */
77
81
  addBack: () => getOrpc().locales.addBack.mutationOptions()
78
82
  };
83
+ /** Correcting memory: content writes, so on Live they start a draft (ADR 0049). */
84
+ const memoryMutations = {
85
+ add: () => getOrpc().memories.add.mutationOptions(),
86
+ change: () => getOrpc().memories.change.mutationOptions(),
87
+ delete: () => getOrpc().memories.delete.mutationOptions()
88
+ };
79
89
  const blockMutations = {
80
90
  delete: () => getOrpc().blocks.delete.mutationOptions(),
81
91
  deleteMany: () => getOrpc().blocks.deleteMany.mutationOptions(),
@@ -149,4 +159,4 @@ const fileMutations = {
149
159
  };
150
160
 
151
161
  //#endregion
152
- export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, pageMutations, projectMutations, publicationMutations, repeatableItemMutations, specialistMutations };
162
+ export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, memoryMutations, pageMutations, projectMutations, publicationMutations, repeatableItemMutations, specialistMutations };
@@ -59,6 +59,12 @@ const reads = {
59
59
  fetch: ({ api, signal }, { projectId }) => api.locales.list({ projectId }, { signal }),
60
60
  staleTime: Infinity
61
61
  }),
62
+ memories: entry({
63
+ key: ({ projectId }) => [...queryKeys.memories.list, projectId],
64
+ everyLocale: true,
65
+ fetch: ({ api, signal }, { projectId }) => api.memories.list({ projectId }, { signal }),
66
+ staleTime: Infinity
67
+ }),
62
68
  missingFromLocale: entry({
63
69
  key: ({ projectId }) => [
64
70
  "camox",
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.76.1";
2
+ var version = "0.78.0";
3
3
 
4
4
  //#endregion
5
5
  export { version };