camox 0.63.0 → 0.65.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/core/createApp.d.ts +28 -28
  2. package/dist/core/createLayout.d.ts +1 -1
  3. package/dist/core/derivedRoutes.js +3 -30
  4. package/dist/core/editing/createEditableBlock.d.ts +604 -604
  5. package/dist/features/canvas/canvasCamera.js +11 -2
  6. package/dist/features/canvas/useCanvasCamera.js +14 -10
  7. package/dist/features/page/CuratedPageContent.js +2 -1
  8. package/dist/features/page/UnplacedBlockContent.js +52 -0
  9. package/dist/features/preview/components/PreviewPanel.js +40 -12
  10. package/dist/features/preview/components/SidebarStack.js +78 -9
  11. package/dist/features/preview/strayCaretGuard.js +34 -0
  12. package/dist/features/provider/CoreCamoxProvider.js +5 -3
  13. package/dist/features/routes/pageRuntime.d.ts +2 -0
  14. package/dist/features/routes/pageRuntime.js +8 -5
  15. package/dist/features/runtime/pageApp.js +111 -74
  16. package/dist/features/runtime/pageClient.js +22 -2
  17. package/dist/features/runtime/runtime.d.ts +10 -0
  18. package/dist/features/runtime/runtime.js +314 -4
  19. package/dist/features/runtime/runtimeRoutes.d.ts +5 -0
  20. package/dist/features/runtime/runtimeRoutes.js +5 -0
  21. package/dist/features/studio/components/ChatWidget.js +88 -62
  22. package/dist/features/studio/components/ConnectAgent.js +313 -0
  23. package/dist/features/studio/components/Patrol.js +455 -205
  24. package/dist/features/studio/components/ReviewDraftSheet.js +431 -334
  25. package/dist/features/studio/components/Squareling.js +74 -41
  26. package/dist/features/studio/components/ThreadComposer.js +130 -107
  27. package/dist/features/studio/components/ThreadMessages.js +82 -13
  28. package/dist/features/studio/components/clientLogoPaths.js +10 -0
  29. package/dist/features/studio/findingSentence.js +37 -0
  30. package/dist/features/studio/reportSentence.js +15 -0
  31. package/dist/features/studio/studioStore.js +23 -1
  32. package/dist/features/studio/workingSentence.js +8 -0
  33. package/dist/lib/api-client.d.ts +1 -0
  34. package/dist/lib/auth.js +34 -32
  35. package/dist/lib/queries.js +9 -1
  36. package/dist/lib/server-auth-cookie.js +17 -4
  37. package/dist/lib/studio-reads.d.ts +1 -1
  38. package/dist/lib/studio-reads.js +5 -1
  39. package/dist/package.js +1 -1
  40. package/dist/studio.css +1 -1
  41. package/package.json +15 -4
@@ -14,6 +14,8 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
14
14
  * while it works; the SEO manager is a tall block with the small shape's soft corner, in amber,
15
15
  * scanning down while it works.
16
16
  *
17
+ * A patroller off duty where the editor is sleeps, eyes closed, breathing slowly.
18
+ *
17
19
  * One face flows into the next: its eyes glide to where the next face holds them, opening or
18
20
  * narrowing, and a looping motion settles before the next one starts (`useFaceChange`).
19
21
  */
@@ -67,7 +69,8 @@ function Squareling(props) {
67
69
  status: props.status
68
70
  }) : /* @__PURE__ */ jsx(PatrollerEyes, {
69
71
  eyes: look.eyes,
70
- working: props.status === "working"
72
+ working: props.status === "working",
73
+ asleep: props.status === "asleep"
71
74
  })
72
75
  })
73
76
  });
@@ -183,6 +186,24 @@ const LOOKS = {
183
186
  x: [44, 63],
184
187
  y: 28
185
188
  }
189
+ },
190
+ linkChecker: {
191
+ body: "M32 14H68Q86 14 86 32V68Q86 86 68 86H32Q14 86 14 68V32Q14 14 32 14Z",
192
+ middle: 50,
193
+ fill: "fill-sky-500",
194
+ eyes: {
195
+ x: [40, 59],
196
+ y: 32
197
+ }
198
+ },
199
+ proofreader: {
200
+ body: "M20 10H64L88 34V82Q88 90 80 90H20Q12 90 12 82V18Q12 10 20 10Z",
201
+ middle: 50,
202
+ fill: "fill-rose-500",
203
+ eyes: {
204
+ x: [34, 53],
205
+ y: 38
206
+ }
186
207
  }
187
208
  };
188
209
  const EYE_WIDTH = 11;
@@ -192,60 +213,68 @@ const OPEN = 17;
192
213
  const NARROWED = 6;
193
214
  /** How far down a narrowed eye's middle sits, from the open one's. */
194
215
  const NARROWED_SHIFT = 2.5;
216
+ const CLOSED = 2;
217
+ /** How far down a closed eye's middle sits, from the open one's: near its lower lid. */
218
+ const CLOSED_SHIFT = 5;
195
219
  /**
196
- * One eye, its left edge at `x` and its top at `y`: open, or narrowed on the work, lower down.
197
- * It narrows by scaling, which `studio.css` eases. Only an eye that blinks carries the class
198
- * `studio.css` blinks.
220
+ * One eye, its left edge at `x` and its top at `y`: open, narrowed on the work, or closed, each
221
+ * lower down. It narrows and closes by scaling, which `studio.css` eases. Only an eye that blinks
222
+ * carries the class `studio.css` blinks.
199
223
  */
200
224
  function Eye(t0) {
201
- const $ = c(9);
202
- const { x, y, narrowed, blinks } = t0;
203
- const t1 = blinks && "squareling-blinks";
204
- let t2;
205
- if ($[0] !== t1) {
206
- t2 = cn("squareling-eye", t1);
207
- $[0] = t1;
208
- $[1] = t2;
209
- } else t2 = $[1];
225
+ const $ = c(7);
226
+ const { x, y, narrowed, closed: t1, blinks } = t0;
227
+ const openness = (t1 === void 0 ? false : t1) ? "closed" : narrowed ? "narrowed" : "open";
228
+ const t2 = blinks && "squareling-blinks";
210
229
  let t3;
211
- if ($[2] !== narrowed) {
212
- t3 = narrowed ? {
213
- scale: `1 ${NARROWED / OPEN}`,
214
- translate: `0 ${NARROWED_SHIFT}px`
215
- } : {
216
- scale: "1",
217
- translate: "0"
218
- };
219
- $[2] = narrowed;
220
- $[3] = t3;
221
- } else t3 = $[3];
222
- let t4;
223
- if ($[4] !== t2 || $[5] !== t3 || $[6] !== x || $[7] !== y) {
224
- t4 = /* @__PURE__ */ jsx("rect", {
225
- className: t2,
230
+ if ($[0] !== t2) {
231
+ t3 = cn("squareling-eye", t2);
232
+ $[0] = t2;
233
+ $[1] = t3;
234
+ } else t3 = $[1];
235
+ const t4 = EYE_STYLES[openness];
236
+ let t5;
237
+ if ($[2] !== t3 || $[3] !== t4 || $[4] !== x || $[5] !== y) {
238
+ t5 = /* @__PURE__ */ jsx("rect", {
239
+ className: t3,
226
240
  x,
227
241
  y,
228
242
  width: EYE_WIDTH,
229
243
  height: OPEN,
230
- style: t3
244
+ style: t4
231
245
  });
232
- $[4] = t2;
233
- $[5] = t3;
234
- $[6] = x;
235
- $[7] = y;
236
- $[8] = t4;
237
- } else t4 = $[8];
238
- return t4;
246
+ $[2] = t3;
247
+ $[3] = t4;
248
+ $[4] = x;
249
+ $[5] = y;
250
+ $[6] = t5;
251
+ } else t5 = $[6];
252
+ return t5;
239
253
  }
254
+ const EYE_STYLES = {
255
+ open: {
256
+ scale: "1",
257
+ translate: "0"
258
+ },
259
+ narrowed: {
260
+ scale: `1 ${NARROWED / OPEN}`,
261
+ translate: `0 ${NARROWED_SHIFT}px`
262
+ },
263
+ closed: {
264
+ scale: `1 ${CLOSED / OPEN}`,
265
+ translate: `0 ${CLOSED_SHIFT}px`
266
+ }
267
+ };
240
268
  /**
241
269
  * The agent's eyes: both narrowed on the work while making changes, the right one while fixing,
242
- * open otherwise, blinking unless it looks around.
270
+ * open otherwise, blinking unless it looks around, makes changes, or checks the design, its eyes
271
+ * fixed on it.
243
272
  */
244
273
  function AgentEyes(t0) {
245
274
  const $ = c(13);
246
275
  const { eyes, status } = t0;
247
276
  const [left, right] = eyes.x;
248
- const blinks = status !== "exploring" && status !== "changing";
277
+ const blinks = status !== "exploring" && status !== "changing" && status !== "checking";
249
278
  const t1 = status === "changing";
250
279
  let t2;
251
280
  if ($[0] !== blinks || $[1] !== eyes.y || $[2] !== left || $[3] !== t1) {
@@ -285,13 +314,17 @@ function AgentEyes(t0) {
285
314
  } else t5 = $[12];
286
315
  return t5;
287
316
  }
288
- /** A patroller's eyes: open while idle, narrowed on the work while working. */
289
- function PatrollerEyes({ eyes, working }) {
317
+ /**
318
+ * A patroller's eyes: open while idle, narrowed on the work while working, closed while asleep,
319
+ * when they don't blink.
320
+ */
321
+ function PatrollerEyes({ eyes, working, asleep }) {
290
322
  return eyes.x.map((x) => /* @__PURE__ */ jsx(Eye, {
291
323
  x,
292
324
  y: eyes.y,
293
325
  narrowed: working,
294
- blinks: true
326
+ closed: asleep,
327
+ blinks: !asleep
295
328
  }, x));
296
329
  }
297
330
 
@@ -1,5 +1,5 @@
1
1
  import { c } from "react/compiler-runtime";
2
- import { useState } from "react";
2
+ import { useEffect, useRef, useState } from "react";
3
3
  import { jsx, jsxs } from "react/jsx-runtime";
4
4
  import { ArrowUp, Square, SquareDashedMousePointer, X } from "lucide-react";
5
5
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea } from "@camox/ui/input-group";
@@ -8,47 +8,69 @@ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea } fro
8
8
  /**
9
9
  * Where an editor writes to the agent: Enter sends, Shift+Enter adds a line. While a run is
10
10
  * going, sending steers it, and a Stop button sits beside Send. Over the message, what the
11
- * editor has selected, which their messages carry to the agent until they clear it.
11
+ * editor has selected, which their messages carry to the agent until they clear it. A message
12
+ * filled in from elsewhere in the studio replaces what the editor wrote, for them to send.
12
13
  */
13
14
  function ThreadComposer(t0) {
14
- const $ = c(36);
15
- const { onSend, onStop, selection, onClearSelection, running: t1, disabled: t2 } = t0;
15
+ const $ = c(39);
16
+ const { onSend, onStop, selection, onClearSelection, running: t1, disabled: t2, filled } = t0;
16
17
  const running = t1 === void 0 ? false : t1;
17
18
  const disabled = t2 === void 0 ? false : t2;
18
19
  const [text, setText] = useState("");
20
+ const [filledId, setFilledId] = useState();
21
+ if (filled && filled.id !== filledId) {
22
+ setFilledId(filled.id);
23
+ setText(filled.text);
24
+ }
25
+ const box = useRef(null);
19
26
  let t3;
20
- if ($[0] !== text) {
21
- t3 = text.trim();
22
- $[0] = text;
27
+ let t4;
28
+ if ($[0] !== filledId) {
29
+ t3 = () => {
30
+ if (filledId !== void 0) box.current?.focus();
31
+ };
32
+ t4 = [filledId];
33
+ $[0] = filledId;
23
34
  $[1] = t3;
24
- } else t3 = $[1];
25
- const message = t3;
35
+ $[2] = t4;
36
+ } else {
37
+ t3 = $[1];
38
+ t4 = $[2];
39
+ }
40
+ useEffect(t3, t4);
41
+ let t5;
42
+ if ($[3] !== text) {
43
+ t5 = text.trim();
44
+ $[3] = text;
45
+ $[4] = t5;
46
+ } else t5 = $[4];
47
+ const message = t5;
26
48
  const canSend = !disabled && message !== "" && onSend !== void 0;
27
- let t4;
28
- if ($[2] !== canSend || $[3] !== message || $[4] !== onSend) {
29
- t4 = () => {
49
+ let t6;
50
+ if ($[5] !== canSend || $[6] !== message || $[7] !== onSend) {
51
+ t6 = () => {
30
52
  if (!canSend) return;
31
53
  onSend(message);
32
54
  setText("");
33
55
  };
34
- $[2] = canSend;
35
- $[3] = message;
36
- $[4] = onSend;
37
- $[5] = t4;
38
- } else t4 = $[5];
39
- const submit = t4;
40
- let t5;
41
- if ($[6] !== submit) {
42
- t5 = (event) => {
56
+ $[5] = canSend;
57
+ $[6] = message;
58
+ $[7] = onSend;
59
+ $[8] = t6;
60
+ } else t6 = $[8];
61
+ const submit = t6;
62
+ let t7;
63
+ if ($[9] !== submit) {
64
+ t7 = (event) => {
43
65
  event.preventDefault();
44
66
  submit();
45
67
  };
46
- $[6] = submit;
47
- $[7] = t5;
48
- } else t5 = $[7];
49
- let t6;
50
- if ($[8] !== onClearSelection || $[9] !== selection) {
51
- t6 = selection && /* @__PURE__ */ jsx(InputGroupAddon, {
68
+ $[9] = submit;
69
+ $[10] = t7;
70
+ } else t7 = $[10];
71
+ let t8;
72
+ if ($[11] !== onClearSelection || $[12] !== selection) {
73
+ t8 = selection && /* @__PURE__ */ jsx(InputGroupAddon, {
52
74
  align: "block-start",
53
75
  className: "border-input border-b",
54
76
  children: /* @__PURE__ */ jsx(SelectionChip, {
@@ -56,44 +78,45 @@ function ThreadComposer(t0) {
56
78
  onClear: onClearSelection
57
79
  })
58
80
  });
59
- $[8] = onClearSelection;
60
- $[9] = selection;
61
- $[10] = t6;
62
- } else t6 = $[10];
63
- let t7;
64
- if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
65
- t7 = (event_0) => setText(event_0.target.value);
66
- $[11] = t7;
67
- } else t7 = $[11];
68
- let t8;
69
- if ($[12] !== submit) {
70
- t8 = (event_1) => {
81
+ $[11] = onClearSelection;
82
+ $[12] = selection;
83
+ $[13] = t8;
84
+ } else t8 = $[13];
85
+ let t9;
86
+ if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
87
+ t9 = (event_0) => setText(event_0.target.value);
88
+ $[14] = t9;
89
+ } else t9 = $[14];
90
+ let t10;
91
+ if ($[15] !== submit) {
92
+ t10 = (event_1) => {
71
93
  if (event_1.key !== "Enter" || event_1.shiftKey || event_1.nativeEvent.isComposing) return;
72
94
  event_1.preventDefault();
73
95
  submit();
74
96
  };
75
- $[12] = submit;
76
- $[13] = t8;
77
- } else t8 = $[13];
78
- let t9;
79
- if ($[14] !== disabled || $[15] !== t8 || $[16] !== text) {
80
- t9 = /* @__PURE__ */ jsx(InputGroupTextarea, {
97
+ $[15] = submit;
98
+ $[16] = t10;
99
+ } else t10 = $[16];
100
+ let t11;
101
+ if ($[17] !== disabled || $[18] !== t10 || $[19] !== text) {
102
+ t11 = /* @__PURE__ */ jsx(InputGroupTextarea, {
103
+ ref: box,
81
104
  "aria-label": "Message",
82
105
  placeholder: "Message Camox…",
83
106
  value: text,
84
107
  disabled,
85
- onChange: t7,
86
- onKeyDown: t8,
108
+ onChange: t9,
109
+ onKeyDown: t10,
87
110
  className: "max-h-48 min-h-14"
88
111
  });
89
- $[14] = disabled;
90
- $[15] = t8;
91
- $[16] = text;
92
- $[17] = t9;
93
- } else t9 = $[17];
94
- let t10;
95
- if ($[18] !== disabled || $[19] !== onStop || $[20] !== running) {
96
- t10 = running && onStop && /* @__PURE__ */ jsxs(InputGroupButton, {
112
+ $[17] = disabled;
113
+ $[18] = t10;
114
+ $[19] = text;
115
+ $[20] = t11;
116
+ } else t11 = $[20];
117
+ let t12;
118
+ if ($[21] !== disabled || $[22] !== onStop || $[23] !== running) {
119
+ t12 = running && onStop && /* @__PURE__ */ jsxs(InputGroupButton, {
97
120
  type: "button",
98
121
  variant: "outline",
99
122
  size: "icon-sm",
@@ -104,73 +127,73 @@ function ThreadComposer(t0) {
104
127
  children: "Stop"
105
128
  })]
106
129
  });
107
- $[18] = disabled;
108
- $[19] = onStop;
109
- $[20] = running;
110
- $[21] = t10;
111
- } else t10 = $[21];
112
- const t11 = !canSend;
113
- let t12;
114
- let t13;
115
- if ($[22] === Symbol.for("react.memo_cache_sentinel")) {
116
- t12 = /* @__PURE__ */ jsx(ArrowUp, {});
117
- t13 = /* @__PURE__ */ jsx("span", {
130
+ $[21] = disabled;
131
+ $[22] = onStop;
132
+ $[23] = running;
133
+ $[24] = t12;
134
+ } else t12 = $[24];
135
+ const t13 = !canSend;
136
+ let t14;
137
+ let t15;
138
+ if ($[25] === Symbol.for("react.memo_cache_sentinel")) {
139
+ t14 = /* @__PURE__ */ jsx(ArrowUp, {});
140
+ t15 = /* @__PURE__ */ jsx("span", {
118
141
  className: "sr-only",
119
142
  children: "Send"
120
143
  });
121
- $[22] = t12;
122
- $[23] = t13;
144
+ $[25] = t14;
145
+ $[26] = t15;
123
146
  } else {
124
- t12 = $[22];
125
- t13 = $[23];
147
+ t14 = $[25];
148
+ t15 = $[26];
126
149
  }
127
- let t14;
128
- if ($[24] !== t11) {
129
- t14 = /* @__PURE__ */ jsxs(InputGroupButton, {
150
+ let t16;
151
+ if ($[27] !== t13) {
152
+ t16 = /* @__PURE__ */ jsxs(InputGroupButton, {
130
153
  type: "submit",
131
154
  variant: "default",
132
155
  size: "icon-sm",
133
- disabled: t11,
134
- children: [t12, t13]
156
+ disabled: t13,
157
+ children: [t14, t15]
135
158
  });
136
- $[24] = t11;
137
- $[25] = t14;
138
- } else t14 = $[25];
139
- let t15;
140
- if ($[26] !== t10 || $[27] !== t14) {
141
- t15 = /* @__PURE__ */ jsxs(InputGroupAddon, {
159
+ $[27] = t13;
160
+ $[28] = t16;
161
+ } else t16 = $[28];
162
+ let t17;
163
+ if ($[29] !== t12 || $[30] !== t16) {
164
+ t17 = /* @__PURE__ */ jsxs(InputGroupAddon, {
142
165
  align: "block-end",
143
166
  className: "justify-end",
144
- children: [t10, t14]
167
+ children: [t12, t16]
145
168
  });
146
- $[26] = t10;
147
- $[27] = t14;
148
- $[28] = t15;
149
- } else t15 = $[28];
150
- let t16;
151
- if ($[29] !== t15 || $[30] !== t6 || $[31] !== t9) {
152
- t16 = /* @__PURE__ */ jsxs(InputGroup, { children: [
153
- t6,
154
- t9,
155
- t15
169
+ $[29] = t12;
170
+ $[30] = t16;
171
+ $[31] = t17;
172
+ } else t17 = $[31];
173
+ let t18;
174
+ if ($[32] !== t11 || $[33] !== t17 || $[34] !== t8) {
175
+ t18 = /* @__PURE__ */ jsxs(InputGroup, { children: [
176
+ t8,
177
+ t11,
178
+ t17
156
179
  ] });
157
- $[29] = t15;
158
- $[30] = t6;
159
- $[31] = t9;
160
- $[32] = t16;
161
- } else t16 = $[32];
162
- let t17;
163
- if ($[33] !== t16 || $[34] !== t5) {
164
- t17 = /* @__PURE__ */ jsx("form", {
180
+ $[32] = t11;
181
+ $[33] = t17;
182
+ $[34] = t8;
183
+ $[35] = t18;
184
+ } else t18 = $[35];
185
+ let t19;
186
+ if ($[36] !== t18 || $[37] !== t7) {
187
+ t19 = /* @__PURE__ */ jsx("form", {
165
188
  className: "p-2",
166
- onSubmit: t5,
167
- children: t16
189
+ onSubmit: t7,
190
+ children: t18
168
191
  });
169
- $[33] = t16;
170
- $[34] = t5;
171
- $[35] = t17;
172
- } else t17 = $[35];
173
- return t17;
192
+ $[36] = t18;
193
+ $[37] = t7;
194
+ $[38] = t19;
195
+ } else t19 = $[38];
196
+ return t19;
174
197
  }
175
198
  /** The selection the editor's messages carry, by its name. */
176
199
  function SelectionChip(t0) {
@@ -3,7 +3,7 @@ import { Squareling } from "./Squareling.js";
3
3
  import { ThreadMarkdown } from "./ThreadMarkdown.js";
4
4
  import { c } from "react/compiler-runtime";
5
5
  import * as React from "react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { Avatar, AvatarFallback, AvatarImage } from "@camox/ui/avatar";
8
8
  import { Bubble, BubbleContent } from "@camox/ui/bubble";
9
9
  import { Message, MessageAvatar, MessageContent, MessageHeader } from "@camox/ui/message";
@@ -274,6 +274,7 @@ const SQUARELING_STATUSES = {
274
274
  exploring: "exploring",
275
275
  changing: "changing",
276
276
  building: "building",
277
+ checking: "checking",
277
278
  fixing: "fixing",
278
279
  startingDraft: "changing"
279
280
  };
@@ -282,6 +283,7 @@ const ACTIVITY_LINES = {
282
283
  exploring: "Looking around…",
283
284
  changing: "Making changes…",
284
285
  building: "Building…",
286
+ checking: "Checking the design…",
285
287
  fixing: "Fixing the build…",
286
288
  startingDraft: "Starting a draft…"
287
289
  };
@@ -419,10 +421,7 @@ function ThreadMessageRow(t0) {
419
421
  t4 = /* @__PURE__ */ jsx(Bubble, {
420
422
  variant: "muted",
421
423
  className: "max-w-full",
422
- children: /* @__PURE__ */ jsx(BubbleContent, {
423
- className: "whitespace-pre-wrap",
424
- children: message.text
425
- })
424
+ children: /* @__PURE__ */ jsx(BubbleContent, { children: /* @__PURE__ */ jsx(CroppedText, { text: message.text }) })
426
425
  });
427
426
  $[29] = message.text;
428
427
  $[30] = t4;
@@ -450,10 +449,7 @@ function ThreadMessageRow(t0) {
450
449
  t2 = /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsxs(MessageContent, { children: [t1, /* @__PURE__ */ jsx(Bubble, {
451
450
  variant: "muted",
452
451
  className: "max-w-full",
453
- children: /* @__PURE__ */ jsx(BubbleContent, {
454
- className: "whitespace-pre-wrap",
455
- children: message.text
456
- })
452
+ children: /* @__PURE__ */ jsx(BubbleContent, { children: /* @__PURE__ */ jsx(CroppedText, { text: message.text }) })
457
453
  })] }) });
458
454
  $[35] = message.text;
459
455
  $[36] = t2;
@@ -472,10 +468,7 @@ function ThreadMessageRow(t0) {
472
468
  align: "end",
473
469
  variant: "muted",
474
470
  className: "max-w-full",
475
- children: /* @__PURE__ */ jsx(BubbleContent, {
476
- className: "whitespace-pre-wrap",
477
- children: message.text
478
- })
471
+ children: /* @__PURE__ */ jsx(BubbleContent, { children: /* @__PURE__ */ jsx(CroppedText, { text: message.text }) })
479
472
  }) });
480
473
  $[39] = message.text;
481
474
  $[40] = t2;
@@ -498,6 +491,82 @@ function _temp(subject, index) {
498
491
  children: subject
499
492
  }, index);
500
493
  }
494
+ /**
495
+ * A message's text, cropped past its fourth line: the next ones fade out, above a button that
496
+ * shows it all. A message that fits shows as is.
497
+ */
498
+ function CroppedText(t0) {
499
+ const $ = c(17);
500
+ const { text } = t0;
501
+ const node = React.useRef(null);
502
+ const [overflows, setOverflows] = React.useState(false);
503
+ const [expanded, setExpanded] = React.useState(false);
504
+ let t1;
505
+ if ($[0] !== expanded) {
506
+ t1 = () => {
507
+ const element = node.current;
508
+ if (!element || expanded) return;
509
+ const measure = () => setOverflows(element.scrollHeight > element.clientHeight + 1);
510
+ measure();
511
+ const observer = new ResizeObserver(measure);
512
+ observer.observe(element);
513
+ return () => observer.disconnect();
514
+ };
515
+ $[0] = expanded;
516
+ $[1] = t1;
517
+ } else t1 = $[1];
518
+ let t2;
519
+ if ($[2] !== expanded || $[3] !== text) {
520
+ t2 = [text, expanded];
521
+ $[2] = expanded;
522
+ $[3] = text;
523
+ $[4] = t2;
524
+ } else t2 = $[4];
525
+ React.useLayoutEffect(t1, t2);
526
+ const cropped = !expanded && overflows;
527
+ const t3 = cropped || void 0;
528
+ const t4 = !expanded && "max-h-[5.5lh] overflow-hidden";
529
+ const t5 = cropped && "mask-[linear-gradient(to_bottom,black_4lh,transparent)]";
530
+ let t6;
531
+ if ($[5] !== t4 || $[6] !== t5) {
532
+ t6 = cn("whitespace-pre-wrap", t4, t5);
533
+ $[5] = t4;
534
+ $[6] = t5;
535
+ $[7] = t6;
536
+ } else t6 = $[7];
537
+ let t7;
538
+ if ($[8] !== t3 || $[9] !== t6 || $[10] !== text) {
539
+ t7 = /* @__PURE__ */ jsx("div", {
540
+ ref: node,
541
+ "data-thread-cropped": t3,
542
+ className: t6,
543
+ children: text
544
+ });
545
+ $[8] = t3;
546
+ $[9] = t6;
547
+ $[10] = text;
548
+ $[11] = t7;
549
+ } else t7 = $[11];
550
+ let t8;
551
+ if ($[12] !== cropped) {
552
+ t8 = cropped && /* @__PURE__ */ jsx("button", {
553
+ type: "button",
554
+ onClick: () => setExpanded(true),
555
+ className: "focus-visible:ring-ring mt-1 rounded-sm font-medium hover:underline focus-visible:ring-2 focus-visible:outline-none",
556
+ children: "Show full message"
557
+ });
558
+ $[12] = cropped;
559
+ $[13] = t8;
560
+ } else t8 = $[13];
561
+ let t9;
562
+ if ($[14] !== t7 || $[15] !== t8) {
563
+ t9 = /* @__PURE__ */ jsxs(Fragment, { children: [t7, t8] });
564
+ $[14] = t7;
565
+ $[15] = t8;
566
+ $[16] = t9;
567
+ } else t9 = $[16];
568
+ return t9;
569
+ }
501
570
  const initials = (name) => name.split(" ").map((part) => part[0]).join("").toUpperCase().slice(0, 2);
502
571
  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";
503
572
  /**
@@ -0,0 +1,10 @@
1
+ //#region src/features/studio/components/clientLogoPaths.ts
2
+ /** Each client's mark, as one SVG path on a 24×24 box. */
3
+ const CLIENT_LOGO_PATHS = {
4
+ claude: "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z",
5
+ chatgpt: "M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z",
6
+ copilot: "M23.922 16.997C23.061 18.492 18.063 22.02 12 22.02 5.937 22.02.939 18.492.078 16.997A.641.641 0 0 1 0 16.741v-2.869a.883.883 0 0 1 .053-.22c.372-.935 1.347-2.292 2.605-2.656.167-.429.414-1.055.644-1.517a10.098 10.098 0 0 1-.052-1.086c0-1.331.282-2.499 1.132-3.368.397-.406.89-.717 1.474-.952C7.255 2.937 9.248 1.98 11.978 1.98c2.731 0 4.767.957 6.166 2.093.584.235 1.077.546 1.474.952.85.869 1.132 2.037 1.132 3.368 0 .368-.014.733-.052 1.086.23.462.477 1.088.644 1.517 1.258.364 2.233 1.721 2.605 2.656a.841.841 0 0 1 .053.22v2.869a.641.641 0 0 1-.078.256Zm-11.75-5.992h-.344a4.359 4.359 0 0 1-.355.508c-.77.947-1.918 1.492-3.508 1.492-1.725 0-2.989-.359-3.782-1.259a2.137 2.137 0 0 1-.085-.104L4 11.746v6.585c1.435.779 4.514 2.179 8 2.179 3.486 0 6.565-1.4 8-2.179v-6.585l-.098-.104s-.033.045-.085.104c-.793.9-2.057 1.259-3.782 1.259-1.59 0-2.738-.545-3.508-1.492a4.359 4.359 0 0 1-.355-.508Zm2.328 3.25c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm-5 0c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm3.313-6.185c.136 1.057.403 1.913.878 2.497.442.544 1.134.938 2.344.938 1.573 0 2.292-.337 2.657-.751.384-.435.558-1.15.558-2.361 0-1.14-.243-1.847-.705-2.319-.477-.488-1.319-.862-2.824-1.025-1.487-.161-2.192.138-2.533.529-.269.307-.437.808-.438 1.578v.021c0 .265.021.562.063.893Zm-1.626 0c.042-.331.063-.628.063-.894v-.02c-.001-.77-.169-1.271-.438-1.578-.341-.391-1.046-.69-2.533-.529-1.505.163-2.347.537-2.824 1.025-.462.472-.705 1.179-.705 2.319 0 1.211.175 1.926.558 2.361.365.414 1.084.751 2.657.751 1.21 0 1.902-.394 2.344-.938.475-.584.742-1.44.878-2.497Z"
7
+ };
8
+
9
+ //#endregion
10
+ export { CLIENT_LOGO_PATHS };
@@ -0,0 +1,37 @@
1
+ import { localeName } from "./localeOptions.js";
2
+ import { findingSentence } from "@camox/api-contract/patrol";
3
+
4
+ //#region src/features/studio/findingSentence.ts
5
+ /** Quoted as editors read it. */
6
+ const quoted = (text) => `“${text}”`;
7
+ /** What a dismissal dismissed, briefly: "Links to About", or the text it was about. */
8
+ function dismissedSentence(about) {
9
+ switch (about.kind) {
10
+ case "missingPage": return `Links to ${about.page ?? "a missing page"}`;
11
+ case "brokenLink": return `Links to ${about.url}`;
12
+ default: return quoted(about.text);
13
+ }
14
+ }
15
+ /** What dismissing a finding says, as its button says it: a broken link's is "This link is fine". */
16
+ function dismissLabel(about) {
17
+ return about.kind === "brokenLink" ? "This link is fine" : "Dismiss";
18
+ }
19
+ /** What applying a fix does, as its button says it. */
20
+ function fixLabel(fix) {
21
+ switch (fix.kind) {
22
+ case "removeLink": return "Remove the link";
23
+ case "replaceText": return `Replace with ${quoted(fix.replacement)}`;
24
+ }
25
+ }
26
+ /**
27
+ * What "Ask the agent" fills the draft's chat with: who found what, where, and the fix it
28
+ * suggests, for the agent to find among the draft's findings and fix.
29
+ */
30
+ function askAgentMessage(finding, role) {
31
+ const where = `(in ${localeName(finding.locale)}, field ${quoted(finding.field)})`;
32
+ const fix = finding.fix ? ` Suggested fix: ${fixLabel(finding.fix)}.` : "";
33
+ return `${role} finding: ${findingSentence(finding)} ${where}.${fix}`;
34
+ }
35
+
36
+ //#endregion
37
+ export { askAgentMessage, dismissLabel, dismissedSentence, findingSentence, fixLabel };