camox 0.78.0 → 0.79.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.
@@ -6,6 +6,7 @@ import { previewStore } from "../preview/previewStore.js";
6
6
  import { studioStore } from "../studio/studioStore.js";
7
7
  import { timeAgo } from "../studio/draftLabel.js";
8
8
  import { useWebsiteHref } from "../studio/components/Navbar.js";
9
+ import { VoiceGroup } from "./VoiceGroup.js";
9
10
  import { c } from "react/compiler-runtime";
10
11
  import * as React from "react";
11
12
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -76,7 +77,7 @@ const Loading = () => {
76
77
  };
77
78
  const words = (count) => `${count.toLocaleString("en")} word${count === 1 ? "" : "s"}`;
78
79
  function ProjectMemory(t0) {
79
- const $ = c(19);
80
+ const $ = c(21);
80
81
  const { projectId } = t0;
81
82
  let t1;
82
83
  if ($[0] !== projectId) {
@@ -95,41 +96,47 @@ function ProjectMemory(t0) {
95
96
  }
96
97
  const room = Math.max(data.limitWords - data.usedWords, 0);
97
98
  let t2;
99
+ let t3;
98
100
  if ($[3] !== projectId) {
99
- t2 = /* @__PURE__ */ jsx(AddMemory, { projectId });
101
+ t2 = /* @__PURE__ */ jsx(VoiceGroup, { projectId });
102
+ t3 = /* @__PURE__ */ jsx(AddMemory, { projectId });
100
103
  $[3] = projectId;
101
104
  $[4] = t2;
102
- } else t2 = $[4];
103
- let t3;
104
- if ($[5] !== room) {
105
- t3 = words(room);
106
- $[5] = room;
107
- $[6] = t3;
108
- } else t3 = $[6];
105
+ $[5] = t3;
106
+ } else {
107
+ t2 = $[4];
108
+ t3 = $[5];
109
+ }
109
110
  let t4;
110
- if ($[7] !== data.limitWords) {
111
- t4 = data.limitWords.toLocaleString("en");
112
- $[7] = data.limitWords;
113
- $[8] = t4;
114
- } else t4 = $[8];
111
+ if ($[6] !== room) {
112
+ t4 = words(room);
113
+ $[6] = room;
114
+ $[7] = t4;
115
+ } else t4 = $[7];
115
116
  let t5;
116
- if ($[9] !== t3 || $[10] !== t4) {
117
- t5 = /* @__PURE__ */ jsxs("p", {
117
+ if ($[8] !== data.limitWords) {
118
+ t5 = data.limitWords.toLocaleString("en");
119
+ $[8] = data.limitWords;
120
+ $[9] = t5;
121
+ } else t5 = $[9];
122
+ let t6;
123
+ if ($[10] !== t4 || $[11] !== t5) {
124
+ t6 = /* @__PURE__ */ jsxs("p", {
118
125
  "data-memory-room": true,
119
126
  className: "text-muted-foreground text-xs",
120
127
  children: [
121
- t3,
128
+ t4,
122
129
  " left of ",
123
- t4
130
+ t5
124
131
  ]
125
132
  });
126
- $[9] = t3;
127
133
  $[10] = t4;
128
134
  $[11] = t5;
129
- } else t5 = $[11];
130
- let t6;
131
- if ($[12] !== data.memories || $[13] !== projectId) {
132
- t6 = data.memories.length === 0 ? /* @__PURE__ */ jsx("p", {
135
+ $[12] = t6;
136
+ } else t6 = $[12];
137
+ let t7;
138
+ if ($[13] !== data.memories || $[14] !== projectId) {
139
+ t7 = data.memories.length === 0 ? /* @__PURE__ */ jsx("p", {
133
140
  "data-memory-empty": true,
134
141
  className: "text-muted-foreground py-6 text-sm",
135
142
  children: "Camox's agent remembers what members teach it that holds beyond one request and that the website can't show, such as how the business talks, what sets it apart, or rules like \"never use usted\". What it remembers in a draft applies there at once, and reaches the live website when the draft is published. Nothing yet: it learns as you work with it, or add a memory yourself."
@@ -141,23 +148,25 @@ function ProjectMemory(t0) {
141
148
  memory
142
149
  }, memory.id))
143
150
  });
144
- $[12] = data.memories;
145
- $[13] = projectId;
146
- $[14] = t6;
147
- } else t6 = $[14];
148
- let t7;
149
- if ($[15] !== t2 || $[16] !== t5 || $[17] !== t6) {
150
- t7 = /* @__PURE__ */ jsxs(Fragment, { children: [
151
+ $[13] = data.memories;
152
+ $[14] = projectId;
153
+ $[15] = t7;
154
+ } else t7 = $[15];
155
+ let t8;
156
+ if ($[16] !== t2 || $[17] !== t3 || $[18] !== t6 || $[19] !== t7) {
157
+ t8 = /* @__PURE__ */ jsxs(Fragment, { children: [
151
158
  t2,
152
- t5,
153
- t6
159
+ t3,
160
+ t6,
161
+ t7
154
162
  ] });
155
- $[15] = t2;
156
- $[16] = t5;
157
- $[17] = t6;
158
- $[18] = t7;
159
- } else t7 = $[18];
160
- return t7;
163
+ $[16] = t2;
164
+ $[17] = t3;
165
+ $[18] = t6;
166
+ $[19] = t7;
167
+ $[20] = t8;
168
+ } else t8 = $[20];
169
+ return t8;
161
170
  }
162
171
  /** Refreshes the memory a write changed. On Live, the write started a draft, which it reads. */
163
172
  function useRefreshMemory(projectId) {
@@ -0,0 +1,350 @@
1
+ import { useDraftScope } from "../../lib/draft-scope.js";
2
+ import { useNavigate } from "../navigation/navigation.js";
3
+ import { reads, useReadQuery, useStudioCache } from "../../lib/studio-reads.js";
4
+ import { memoryMutations } from "../../lib/queries.js";
5
+ import { previewStore } from "../preview/previewStore.js";
6
+ import { useWebsiteHref } from "../studio/components/Navbar.js";
7
+ import { c } from "react/compiler-runtime";
8
+ import * as React from "react";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { useMutation } from "@tanstack/react-query";
11
+ import { Button } from "@camox/ui/button";
12
+ import { Skeleton } from "@camox/ui/skeleton";
13
+ import { Spinner } from "@camox/ui/spinner";
14
+ import { VOICE_AXES } from "@camox/api-contract/voice";
15
+ import { Slider } from "@camox/ui/slider";
16
+
17
+ //#region src/features/memory/VoiceGroup.tsx
18
+ /**
19
+ * The Voice group, at the top of the Memory Section (ADR 0051): a slider per Voice Axis, showing
20
+ * its chosen step and that step's definition, as models read it. Moving sliders saves nothing:
21
+ * Save shows while they differ from the saved Voice, and saves every moved axis as one change.
22
+ * Like any memory edit, saving while viewing Live starts a draft. "Save and rewrite", beside it,
23
+ * also asks Camox's agent, as the member, to rewrite the site's copy in the new Voice, and shows
24
+ * the draft with its thread beside the Canvas, where the rewrite happens.
25
+ */
26
+ function VoiceGroup(t0) {
27
+ const $ = c(31);
28
+ const { projectId } = t0;
29
+ let t1;
30
+ if ($[0] !== projectId) {
31
+ t1 = reads.voice({ projectId });
32
+ $[0] = projectId;
33
+ $[1] = t1;
34
+ } else t1 = $[1];
35
+ const { data: saved } = useReadQuery(t1);
36
+ let t2;
37
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
38
+ t2 = {};
39
+ $[2] = t2;
40
+ } else t2 = $[2];
41
+ const [moved, setMoved] = React.useState(t2);
42
+ const cache = useStudioCache();
43
+ const draftScope = useDraftScope();
44
+ const navigate = useNavigate();
45
+ const websiteHref = useWebsiteHref();
46
+ let t3;
47
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
48
+ t3 = memoryMutations.saveVoice();
49
+ $[3] = t3;
50
+ } else t3 = $[3];
51
+ let t4;
52
+ if ($[4] !== cache || $[5] !== draftScope || $[6] !== navigate || $[7] !== projectId || $[8] !== websiteHref) {
53
+ t4 = {
54
+ ...t3,
55
+ onSuccess: (_voice, t5) => {
56
+ const { rewrite } = t5;
57
+ setMoved({});
58
+ cache.invalidate(reads.voice({ projectId }));
59
+ if (!rewrite) return;
60
+ previewStore.send({ type: "showCanvas" });
61
+ navigate({
62
+ to: websiteHref(),
63
+ draft: draftScope.current() ?? void 0
64
+ });
65
+ }
66
+ };
67
+ $[4] = cache;
68
+ $[5] = draftScope;
69
+ $[6] = navigate;
70
+ $[7] = projectId;
71
+ $[8] = websiteHref;
72
+ $[9] = t4;
73
+ } else t4 = $[9];
74
+ const save = useMutation(t4);
75
+ if (!saved) {
76
+ let t5;
77
+ if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
78
+ t5 = /* @__PURE__ */ jsx(Skeleton, { className: "h-72" });
79
+ $[10] = t5;
80
+ } else t5 = $[10];
81
+ return t5;
82
+ }
83
+ let t5;
84
+ let voice;
85
+ if ($[11] !== moved || $[12] !== saved) {
86
+ voice = {
87
+ ...saved,
88
+ ...moved
89
+ };
90
+ t5 = changedAxes(saved, voice);
91
+ $[11] = moved;
92
+ $[12] = saved;
93
+ $[13] = t5;
94
+ $[14] = voice;
95
+ } else {
96
+ t5 = $[13];
97
+ voice = $[14];
98
+ }
99
+ const changed = t5;
100
+ let t6;
101
+ if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
102
+ t6 = /* @__PURE__ */ jsxs("header", {
103
+ className: "flex flex-col gap-1",
104
+ children: [/* @__PURE__ */ jsx("h2", {
105
+ id: "camox-voice-title",
106
+ className: "text-sm font-semibold",
107
+ children: "Voice"
108
+ }), /* @__PURE__ */ jsx("p", {
109
+ className: "text-muted-foreground text-xs",
110
+ children: "How the website speaks. Every model that writes for visitors keeps to it."
111
+ })]
112
+ });
113
+ $[15] = t6;
114
+ } else t6 = $[15];
115
+ let t7;
116
+ if ($[16] !== save || $[17] !== voice) {
117
+ t7 = VOICE_AXES.map((axis) => /* @__PURE__ */ jsx(VoiceAxisRow, {
118
+ axis,
119
+ step: voice[axis.key],
120
+ disabled: save.isPending,
121
+ onStep: (step) => {
122
+ setMoved((current) => ({
123
+ ...current,
124
+ [axis.key]: step
125
+ }));
126
+ save.reset();
127
+ }
128
+ }, axis.key));
129
+ $[16] = save;
130
+ $[17] = voice;
131
+ $[18] = t7;
132
+ } else t7 = $[18];
133
+ let t8;
134
+ if ($[19] !== t7) {
135
+ t8 = /* @__PURE__ */ jsx("ul", {
136
+ className: "flex flex-col gap-5",
137
+ children: t7
138
+ });
139
+ $[19] = t7;
140
+ $[20] = t8;
141
+ } else t8 = $[20];
142
+ let t9;
143
+ if ($[21] !== save.error) {
144
+ t9 = save.error && /* @__PURE__ */ jsx("p", {
145
+ "data-voice-error": true,
146
+ className: "text-destructive text-xs",
147
+ children: save.error.message
148
+ });
149
+ $[21] = save.error;
150
+ $[22] = t9;
151
+ } else t9 = $[22];
152
+ let t10;
153
+ if ($[23] !== changed || $[24] !== projectId || $[25] !== save) {
154
+ t10 = Object.keys(changed).length > 0 && /* @__PURE__ */ jsxs("div", {
155
+ className: "flex flex-wrap gap-2",
156
+ children: [/* @__PURE__ */ jsxs(Button, {
157
+ disabled: save.isPending,
158
+ onClick: () => save.mutate({
159
+ projectId,
160
+ steps: changed
161
+ }),
162
+ children: [save.isPending && !save.variables?.rewrite && /* @__PURE__ */ jsx(Spinner, {}), "Save"]
163
+ }), /* @__PURE__ */ jsxs(Button, {
164
+ variant: "outline",
165
+ disabled: save.isPending,
166
+ onClick: () => save.mutate({
167
+ projectId,
168
+ steps: changed,
169
+ rewrite: true
170
+ }),
171
+ children: [save.isPending && save.variables?.rewrite && /* @__PURE__ */ jsx(Spinner, {}), "Save and rewrite"]
172
+ })]
173
+ });
174
+ $[23] = changed;
175
+ $[24] = projectId;
176
+ $[25] = save;
177
+ $[26] = t10;
178
+ } else t10 = $[26];
179
+ let t11;
180
+ if ($[27] !== t10 || $[28] !== t8 || $[29] !== t9) {
181
+ t11 = /* @__PURE__ */ jsxs("section", {
182
+ "data-voice": true,
183
+ "aria-labelledby": "camox-voice-title",
184
+ className: "flex flex-col gap-5 rounded-lg border p-4",
185
+ children: [
186
+ t6,
187
+ t8,
188
+ t9,
189
+ t10
190
+ ]
191
+ });
192
+ $[27] = t10;
193
+ $[28] = t8;
194
+ $[29] = t9;
195
+ $[30] = t11;
196
+ } else t11 = $[30];
197
+ return t11;
198
+ }
199
+ /** The axes `voice` moves away from `saved`, with their new steps. */
200
+ function changedAxes(saved, voice) {
201
+ const changed = {};
202
+ for (const { key } of VOICE_AXES) if (voice[key] !== saved[key]) changed[key] = voice[key];
203
+ return changed;
204
+ }
205
+ const sentenceCase = (text) => text.charAt(0).toUpperCase() + text.slice(1);
206
+ /** One Voice Axis: its name, its chosen step and that step's definition, over a slider. */
207
+ function VoiceAxisRow(t0) {
208
+ const $ = c(39);
209
+ const { axis, step, disabled, onStep } = t0;
210
+ const steps = axis.steps;
211
+ let t1;
212
+ if ($[0] !== step || $[1] !== steps) {
213
+ let t2;
214
+ if ($[3] !== step) {
215
+ t2 = (candidate) => candidate.key === step;
216
+ $[3] = step;
217
+ $[4] = t2;
218
+ } else t2 = $[4];
219
+ t1 = steps.findIndex(t2);
220
+ $[0] = step;
221
+ $[1] = steps;
222
+ $[2] = t1;
223
+ } else t1 = $[2];
224
+ const index = Math.max(t1, 0);
225
+ const chosen = steps[index];
226
+ let t2;
227
+ if ($[5] !== axis.name) {
228
+ t2 = /* @__PURE__ */ jsx("span", {
229
+ "data-voice-axis-name": true,
230
+ className: "font-medium",
231
+ children: axis.name
232
+ });
233
+ $[5] = axis.name;
234
+ $[6] = t2;
235
+ } else t2 = $[6];
236
+ let t3;
237
+ if ($[7] !== chosen.name) {
238
+ t3 = /* @__PURE__ */ jsx("span", {
239
+ "data-voice-step": true,
240
+ children: chosen.name
241
+ });
242
+ $[7] = chosen.name;
243
+ $[8] = t3;
244
+ } else t3 = $[8];
245
+ let t4;
246
+ if ($[9] !== t2 || $[10] !== t3) {
247
+ t4 = /* @__PURE__ */ jsxs("div", {
248
+ className: "flex items-baseline justify-between gap-3 text-sm",
249
+ children: [t2, t3]
250
+ });
251
+ $[9] = t2;
252
+ $[10] = t3;
253
+ $[11] = t4;
254
+ } else t4 = $[11];
255
+ const t5 = steps.length - 1;
256
+ let t6;
257
+ if ($[12] !== onStep || $[13] !== steps) {
258
+ t6 = (value) => {
259
+ const next = steps[value];
260
+ if (next) onStep(next.key);
261
+ };
262
+ $[12] = onStep;
263
+ $[13] = steps;
264
+ $[14] = t6;
265
+ } else t6 = $[14];
266
+ let t7;
267
+ if ($[15] !== axis.name || $[16] !== chosen.name || $[17] !== disabled || $[18] !== index || $[19] !== steps.length || $[20] !== t5 || $[21] !== t6) {
268
+ t7 = /* @__PURE__ */ jsx(Slider, {
269
+ label: axis.name,
270
+ valueText: chosen.name,
271
+ min: 0,
272
+ max: t5,
273
+ step: 1,
274
+ marks: steps.length,
275
+ value: index,
276
+ disabled,
277
+ onValueChange: t6
278
+ });
279
+ $[15] = axis.name;
280
+ $[16] = chosen.name;
281
+ $[17] = disabled;
282
+ $[18] = index;
283
+ $[19] = steps.length;
284
+ $[20] = t5;
285
+ $[21] = t6;
286
+ $[22] = t7;
287
+ } else t7 = $[22];
288
+ let t8;
289
+ if ($[23] !== steps[0].name) {
290
+ t8 = /* @__PURE__ */ jsx("span", { children: steps[0].name });
291
+ $[23] = steps[0].name;
292
+ $[24] = t8;
293
+ } else t8 = $[24];
294
+ const t9 = steps[steps.length - 1];
295
+ let t10;
296
+ if ($[25] !== t9.name) {
297
+ t10 = /* @__PURE__ */ jsx("span", { children: t9.name });
298
+ $[25] = t9.name;
299
+ $[26] = t10;
300
+ } else t10 = $[26];
301
+ let t11;
302
+ if ($[27] !== t10 || $[28] !== t8) {
303
+ t11 = /* @__PURE__ */ jsxs("div", {
304
+ "aria-hidden": true,
305
+ className: "text-muted-foreground flex justify-between text-[11px]",
306
+ children: [t8, t10]
307
+ });
308
+ $[27] = t10;
309
+ $[28] = t8;
310
+ $[29] = t11;
311
+ } else t11 = $[29];
312
+ let t12;
313
+ if ($[30] !== chosen.definition) {
314
+ t12 = sentenceCase(chosen.definition);
315
+ $[30] = chosen.definition;
316
+ $[31] = t12;
317
+ } else t12 = $[31];
318
+ let t13;
319
+ if ($[32] !== t12) {
320
+ t13 = /* @__PURE__ */ jsx("p", {
321
+ "data-voice-definition": true,
322
+ className: "text-muted-foreground text-xs",
323
+ children: t12
324
+ });
325
+ $[32] = t12;
326
+ $[33] = t13;
327
+ } else t13 = $[33];
328
+ let t14;
329
+ if ($[34] !== t11 || $[35] !== t13 || $[36] !== t4 || $[37] !== t7) {
330
+ t14 = /* @__PURE__ */ jsxs("li", {
331
+ "data-voice-axis": true,
332
+ className: "flex flex-col gap-2",
333
+ children: [
334
+ t4,
335
+ t7,
336
+ t11,
337
+ t13
338
+ ]
339
+ });
340
+ $[34] = t11;
341
+ $[35] = t13;
342
+ $[36] = t4;
343
+ $[37] = t7;
344
+ $[38] = t14;
345
+ } else t14 = $[38];
346
+ return t14;
347
+ }
348
+
349
+ //#endregion
350
+ export { VoiceGroup };
@@ -16,15 +16,16 @@ import { toast } from "@camox/ui/toaster";
16
16
  import { Skeleton } from "@camox/ui/skeleton";
17
17
  import { Spinner } from "@camox/ui/spinner";
18
18
  import { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from "@camox/ui/popover";
19
+ import { voiceAxis, voiceStep } from "@camox/api-contract/voice";
19
20
 
20
21
  //#region src/features/studio/components/DraftReview.tsx
21
22
  /**
22
- * Where a subject sits: its page or layout, or the files, the languages or memory, which belong
23
- * to none.
23
+ * Where a subject sits: its page or layout, or the files, the languages or memory (the Voice
24
+ * included), which belong to none.
24
25
  */
25
26
  function ownerName(owner, entity) {
26
27
  if (entity === "locale") return "Languages";
27
- if (entity === "memory") return "Memory";
28
+ if (entity === "memory" || entity === "voiceAxis") return "Memory";
28
29
  if (!owner) return entity === "file" ? "Files" : "Elsewhere";
29
30
  if (owner.kind === "layout") return `Layout ${owner.name}`;
30
31
  if (owner.kind === "syncedBlock") return `Synced block ${owner.title}`;
@@ -219,14 +220,20 @@ const MEMORY_CHANGE_LABELS = {
219
220
  changed: "Changed",
220
221
  deleted: "Deleted"
221
222
  };
223
+ /** A Voice Axis the draft moves, as it reads: "Humor: Earnest → Light". */
224
+ function voiceLine({ axis, before, after }) {
225
+ const stepName = (key) => voiceStep(axis, key).name;
226
+ return `${voiceAxis(axis).name}: ${stepName(before)} → ${stepName(after)}`;
227
+ }
222
228
  /**
223
- * The memories the draft adds, changes and deletes, each with its text, and Live's before a
224
- * change: what Camox's agent will remember once it's published. Nothing when it changes none.
229
+ * What publishing the draft changes in memory: each Voice Axis it moves, from Live's step to the
230
+ * draft's, then the memories it adds, changes and deletes, each with its text, and Live's before a
231
+ * change. Nothing when it changes neither.
225
232
  */
226
233
  function MemoryChanges(t0) {
227
- const $ = c(5);
228
- const { changes } = t0;
229
- if (changes.length === 0) return null;
234
+ const $ = c(8);
235
+ const { voice, changes } = t0;
236
+ if (voice.length === 0 && changes.length === 0) return null;
230
237
  let t1;
231
238
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
232
239
  t1 = /* @__PURE__ */ jsx("h3", {
@@ -236,27 +243,34 @@ function MemoryChanges(t0) {
236
243
  $[0] = t1;
237
244
  } else t1 = $[0];
238
245
  let t2;
239
- if ($[1] !== changes) {
240
- t2 = changes.map(_temp4);
241
- $[1] = changes;
246
+ if ($[1] !== voice) {
247
+ t2 = voice.map(_temp4);
248
+ $[1] = voice;
242
249
  $[2] = t2;
243
250
  } else t2 = $[2];
244
251
  let t3;
245
- if ($[3] !== t2) {
246
- t3 = /* @__PURE__ */ jsxs("section", {
252
+ if ($[3] !== changes) {
253
+ t3 = changes.map(_temp5);
254
+ $[3] = changes;
255
+ $[4] = t3;
256
+ } else t3 = $[4];
257
+ let t4;
258
+ if ($[5] !== t2 || $[6] !== t3) {
259
+ t4 = /* @__PURE__ */ jsxs("section", {
247
260
  "data-memory-changes": true,
248
261
  className: "flex flex-col gap-2",
249
- children: [t1, /* @__PURE__ */ jsx("ul", {
262
+ children: [t1, /* @__PURE__ */ jsxs("ul", {
250
263
  className: "flex flex-col gap-2",
251
- children: t2
264
+ children: [t2, t3]
252
265
  })]
253
266
  });
254
- $[3] = t2;
255
- $[4] = t3;
256
- } else t3 = $[4];
257
- return t3;
267
+ $[5] = t2;
268
+ $[6] = t3;
269
+ $[7] = t4;
270
+ } else t4 = $[7];
271
+ return t4;
258
272
  }
259
- function _temp4(memory) {
273
+ function _temp5(memory) {
260
274
  return /* @__PURE__ */ jsxs("li", {
261
275
  className: "flex flex-col gap-0.5 rounded-lg border p-3 text-sm",
262
276
  children: [
@@ -275,6 +289,12 @@ function _temp4(memory) {
275
289
  ]
276
290
  }, memory.id);
277
291
  }
292
+ function _temp4(change) {
293
+ return /* @__PURE__ */ jsx("li", {
294
+ className: "rounded-lg border p-3 text-sm",
295
+ children: /* @__PURE__ */ jsx("p", { children: voiceLine(change) })
296
+ }, change.axis);
297
+ }
278
298
  /**
279
299
  * The draft's overview: the bullets AI wrote about its changes, in full, saying it's updating
280
300
  * while they describe an older version of the draft. While
@@ -290,7 +310,7 @@ function DraftOverview(t0) {
290
310
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
291
311
  t1 = {
292
312
  ...draftMutations.retryOverview(),
293
- onError: _temp5
313
+ onError: _temp6
294
314
  };
295
315
  $[0] = t1;
296
316
  } else t1 = $[0];
@@ -311,7 +331,7 @@ function DraftOverview(t0) {
311
331
  if (overview.status === "ready") {
312
332
  let t2;
313
333
  if ($[3] !== overview.bullets) {
314
- t2 = overview.bullets.map(_temp6);
334
+ t2 = overview.bullets.map(_temp7);
315
335
  $[3] = overview.bullets;
316
336
  $[4] = t2;
317
337
  } else t2 = $[4];
@@ -352,7 +372,7 @@ function DraftOverview(t0) {
352
372
  if (overview.status === "failed") {
353
373
  let t2;
354
374
  if ($[12] !== changes) {
355
- t2 = new Set(changes.map(_temp7));
375
+ t2 = new Set(changes.map(_temp8));
356
376
  $[12] = changes;
357
377
  $[13] = t2;
358
378
  } else t2 = $[13];
@@ -455,13 +475,13 @@ function DraftOverview(t0) {
455
475
  } else t2 = $[34];
456
476
  return t2;
457
477
  }
458
- function _temp7(change) {
478
+ function _temp8(change) {
459
479
  return ownerName(change.owner, change.entity);
460
480
  }
461
- function _temp6(bullet, index) {
481
+ function _temp7(bullet, index) {
462
482
  return /* @__PURE__ */ jsx("li", { children: bullet }, index);
463
483
  }
464
- function _temp5(error) {
484
+ function _temp6(error) {
465
485
  return toast.error(error.message || "Could not summarize the draft");
466
486
  }
467
487
  /**
@@ -569,8 +589,11 @@ function DraftReview(t0) {
569
589
  showInCanvas(target);
570
590
  }
571
591
  }),
572
- lines.map(_temp8),
573
- /* @__PURE__ */ jsx(MemoryChanges, { changes: data.memoryChanges })
592
+ lines.map(_temp9),
593
+ /* @__PURE__ */ jsx(MemoryChanges, {
594
+ voice: data.voiceChanges,
595
+ changes: data.memoryChanges
596
+ })
574
597
  ]
575
598
  }) : /* @__PURE__ */ jsx("div", {
576
599
  className: cn("flex items-center justify-center", REVIEW_BODY_HEIGHT),
@@ -646,7 +669,7 @@ function DraftReview(t0) {
646
669
  } else t16 = $[50];
647
670
  return t16;
648
671
  }
649
- function _temp8(line) {
672
+ function _temp9(line) {
650
673
  return /* @__PURE__ */ jsx("p", {
651
674
  className: "text-sm text-amber-700 dark:text-amber-400",
652
675
  "data-review-line": true,
@@ -87,6 +87,7 @@ const CAMOX_OPTIMIZED_DEPS = [
87
87
  "camox > @base-ui/react/popover",
88
88
  "camox > @base-ui/react/select",
89
89
  "camox > @base-ui/react/separator",
90
+ "camox > @base-ui/react/slider",
90
91
  "camox > @base-ui/react/switch",
91
92
  "camox > @base-ui/react/tabs",
92
93
  "camox > @base-ui/react/toggle",
@@ -80,11 +80,15 @@ const localeMutations = {
80
80
  /** Adds a page or record deleted from the locale viewed back to it. */
81
81
  addBack: () => getOrpc().locales.addBack.mutationOptions()
82
82
  };
83
- /** Correcting memory: content writes, so on Live they start a draft (ADR 0049). */
83
+ /**
84
+ * Correcting memory and saving the Voice: content writes, so on Live they start a draft (ADR 0049,
85
+ * ADR 0051).
86
+ */
84
87
  const memoryMutations = {
85
88
  add: () => getOrpc().memories.add.mutationOptions(),
86
89
  change: () => getOrpc().memories.change.mutationOptions(),
87
- delete: () => getOrpc().memories.delete.mutationOptions()
90
+ delete: () => getOrpc().memories.delete.mutationOptions(),
91
+ saveVoice: () => getOrpc().memories.saveVoice.mutationOptions()
88
92
  };
89
93
  const blockMutations = {
90
94
  delete: () => getOrpc().blocks.delete.mutationOptions(),
@@ -8,6 +8,7 @@ import "react";
8
8
  import "@camox/api-contract/query-keys";
9
9
  import "@camox/api-contract/specialists";
10
10
  import { QueryClient } from "@tanstack/react-query";
11
+ import "@camox/api-contract/voice";
11
12
  //#region src/lib/studio-reads.d.ts
12
13
  type LayoutRead = Awaited<ReturnType<ApiClient["layouts"]["get"]>>;
13
14
  /** A layout's structure: its blocks before and after the content of its pages. */
@@ -65,6 +65,12 @@ const reads = {
65
65
  fetch: ({ api, signal }, { projectId }) => api.memories.list({ projectId }, { signal }),
66
66
  staleTime: Infinity
67
67
  }),
68
+ voice: entry({
69
+ key: ({ projectId }) => [...queryKeys.memories.voice, projectId],
70
+ everyLocale: true,
71
+ fetch: ({ api, signal }, { projectId }) => api.memories.voice({ projectId }, { signal }),
72
+ staleTime: Infinity
73
+ }),
68
74
  missingFromLocale: entry({
69
75
  key: ({ projectId }) => [
70
76
  "camox",