camox 0.54.0 → 0.57.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 (50) hide show
  1. package/dist/features/canvas/CamoxCanvas.js +14 -11
  2. package/dist/features/canvas/CanvasLoadingScreen.js +1 -1
  3. package/dist/features/canvas/CanvasPageHeader.js +1 -1
  4. package/dist/features/canvas/canvasFrameDocument.js +13 -6
  5. package/dist/features/canvas/sidebar/CanvasViewHeader.js +1 -1
  6. package/dist/features/canvas/sidebar/{CanvasSidebar.js → ContentSidebar.js} +5 -5
  7. package/dist/features/canvas/sidebar/{CanvasSidebarFrame.js → ContentSidebarFrame.js} +4 -4
  8. package/dist/features/canvas/sidebar/{CanvasSidebarSkeleton.js → ContentSidebarSkeleton.js} +7 -7
  9. package/dist/features/preview/CamoxPreview.js +2 -2
  10. package/dist/features/preview/components/Frame.js +1 -18
  11. package/dist/features/preview/components/PreviewToolbar.js +1 -1
  12. package/dist/features/preview/components/SelectionEditor.js +2 -2
  13. package/dist/features/preview/components/SidebarStack.js +201 -116
  14. package/dist/features/preview/components/StudioEditingFallback.js +3 -3
  15. package/dist/features/preview/components/previewStyles.js +25 -1
  16. package/dist/features/renderer/RendererApp.js +55 -37
  17. package/dist/features/routes/pageRuntime.d.ts +1 -1
  18. package/dist/features/runtime/RuntimeChrome.js +4 -4
  19. package/dist/features/studio/components/AgentSidebar.js +56 -0
  20. package/dist/features/studio/components/ChatWidget.js +219 -0
  21. package/dist/features/studio/components/DraftsControl.js +17 -17
  22. package/dist/features/studio/components/Patrol.js +676 -0
  23. package/dist/features/studio/components/ReviewDraftSheet.js +38 -38
  24. package/dist/features/studio/components/Squareling.js +171 -84
  25. package/dist/features/studio/components/ThreadMessages.js +256 -136
  26. package/dist/features/studio/localeOptions.js +7 -3
  27. package/dist/features/studio/reportSentence.js +75 -0
  28. package/dist/features/studio/routes.js +2 -1
  29. package/dist/features/studio/showInCanvas.js +9 -2
  30. package/dist/features/studio/studioStore.js +5 -5
  31. package/dist/features/studio/workingSentence.js +55 -0
  32. package/dist/lib/queries.js +1 -1
  33. package/dist/lib/studio-reads.d.ts +1 -0
  34. package/dist/lib/studio-reads.js +5 -0
  35. package/dist/lib/use-thread.js +156 -190
  36. package/dist/package.js +1 -1
  37. package/dist/studio.css +1 -1
  38. package/package.json +4 -4
  39. package/skills/camox/SKILL.md +3 -10
  40. package/skills/camox/references/artifacts.md +2 -2
  41. package/skills/camox/references/block-definitions.md +1 -1
  42. package/skills/camox/references/layout-definitions.md +1 -1
  43. package/skills/camox/references/routed-layouts.md +2 -2
  44. package/skills/camox-cli/SKILL.md +15 -0
  45. package/skills/{camox → camox-cli}/references/cli-common.md +1 -1
  46. package/skills/{camox → camox-cli}/references/cli-content.md +2 -2
  47. package/skills/{camox → camox-cli}/references/cli-publishing.md +4 -4
  48. package/skills/camox-expand/SKILL.md +1 -1
  49. package/skills/camoxify/SKILL.md +1 -1
  50. package/dist/features/studio/components/ChatSidebar.js +0 -346
@@ -0,0 +1,676 @@
1
+ import { AuthContext } from "../../../lib/auth.js";
2
+ import { localizedPath } from "../../../lib/locale-path.js";
3
+ import { useCurrentDraftId } from "../../../lib/draft-scope.js";
4
+ import { useViewLocale } from "../../../lib/view-locale-context.js";
5
+ import { useNavigate } from "../../navigation/navigation.js";
6
+ import { reads, useReadQuery } from "../../../lib/studio-reads.js";
7
+ import { cn } from "../../../lib/utils.js";
8
+ import { AssetLightbox } from "../../preview/components/AssetLightbox.js";
9
+ import { BOTTOM_CARD_EDGES, CARD_BODY_EDGES } from "../../preview/components/SidebarStack.js";
10
+ import { editCollectionItemPath } from "../routes.js";
11
+ import { timeAgo } from "../draftLabel.js";
12
+ import { useShowInCanvas } from "../showInCanvas.js";
13
+ import { Squareling } from "./Squareling.js";
14
+ import { moreSentence, reportSentence, shortTimeAgo, whereReportLeads } from "../reportSentence.js";
15
+ import { singleLocaleSentence, workingSentence } from "../workingSentence.js";
16
+ import { c } from "react/compiler-runtime";
17
+ import * as React from "react";
18
+ import { jsx, jsxs } from "react/jsx-runtime";
19
+ import { Badge } from "@camox/ui/badge";
20
+ import { reportLocale } from "@camox/api-contract/patrol";
21
+
22
+ //#region src/features/studio/components/Patrol.tsx
23
+ /**
24
+ * The Agent Sidebar's Patrol, over the Chat widget: every patroller of the project, and what each
25
+ * did without being asked in the draft viewed, or on Live (ADR 0024). It fetches the Patrol of the
26
+ * scope viewed, and holds which patrollers are expanded.
27
+ */
28
+ function Patrol(t0) {
29
+ const $ = c(14);
30
+ const { className } = t0;
31
+ const { projectUuid } = React.useContext(AuthContext);
32
+ let t1;
33
+ if ($[0] !== projectUuid) {
34
+ t1 = reads.project({ projectUuid });
35
+ $[0] = projectUuid;
36
+ $[1] = t1;
37
+ } else t1 = $[1];
38
+ const { data: project } = useReadQuery(t1);
39
+ const draftId = useCurrentDraftId();
40
+ let t2;
41
+ if ($[2] !== className) {
42
+ t2 = cn("flex flex-col", className);
43
+ $[2] = className;
44
+ $[3] = t2;
45
+ } else t2 = $[3];
46
+ let t3;
47
+ let t4;
48
+ if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
49
+ t3 = /* @__PURE__ */ jsx("header", {
50
+ className: cn("bg-background flex h-11 min-w-0 shrink-0 items-center justify-center px-3", BOTTOM_CARD_EDGES),
51
+ children: /* @__PURE__ */ jsx("h2", {
52
+ className: "truncate text-sm font-medium",
53
+ children: "Patrol"
54
+ })
55
+ });
56
+ t4 = cn("bg-background min-h-0 flex-1 overflow-y-auto", CARD_BODY_EDGES);
57
+ $[4] = t3;
58
+ $[5] = t4;
59
+ } else {
60
+ t3 = $[4];
61
+ t4 = $[5];
62
+ }
63
+ let t5;
64
+ if ($[6] !== draftId || $[7] !== project) {
65
+ t5 = project && /* @__PURE__ */ jsx(ProjectPatrol, {
66
+ projectId: project.id,
67
+ inDraft: draftId !== null
68
+ });
69
+ $[6] = draftId;
70
+ $[7] = project;
71
+ $[8] = t5;
72
+ } else t5 = $[8];
73
+ let t6;
74
+ if ($[9] !== t5) {
75
+ t6 = /* @__PURE__ */ jsx("div", {
76
+ className: t4,
77
+ children: t5
78
+ });
79
+ $[9] = t5;
80
+ $[10] = t6;
81
+ } else t6 = $[10];
82
+ let t7;
83
+ if ($[11] !== t2 || $[12] !== t6) {
84
+ t7 = /* @__PURE__ */ jsxs("section", {
85
+ "aria-label": "Patrol",
86
+ "data-patrol": true,
87
+ className: t2,
88
+ children: [t3, t6]
89
+ });
90
+ $[11] = t2;
91
+ $[12] = t6;
92
+ $[13] = t7;
93
+ } else t7 = $[13];
94
+ return t7;
95
+ }
96
+ function ProjectPatrol(t0) {
97
+ const $ = c(28);
98
+ const { projectId, inDraft } = t0;
99
+ let t1;
100
+ if ($[0] !== projectId) {
101
+ t1 = reads.patrol({ projectId });
102
+ $[0] = projectId;
103
+ $[1] = t1;
104
+ } else t1 = $[1];
105
+ const { data: view } = useReadQuery(t1);
106
+ let t2;
107
+ if ($[2] !== projectId) {
108
+ t2 = reads.localeList({ projectId });
109
+ $[2] = projectId;
110
+ $[3] = t2;
111
+ } else t2 = $[3];
112
+ const { data: locales } = useReadQuery(t2);
113
+ let t3;
114
+ if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
115
+ t3 = /* @__PURE__ */ new Set();
116
+ $[4] = t3;
117
+ } else t3 = $[4];
118
+ const [expanded, setExpanded] = React.useState(t3);
119
+ let t4;
120
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
121
+ t4 = (id) => setExpanded((previous) => {
122
+ const next = new Set(previous);
123
+ if (!next.delete(id)) next.add(id);
124
+ return next;
125
+ });
126
+ $[5] = t4;
127
+ } else t4 = $[5];
128
+ const toggle = t4;
129
+ const now = useNow(3e4);
130
+ const [heldOrder, setHeldOrder] = React.useState(null);
131
+ let t5;
132
+ if ($[6] !== heldOrder || $[7] !== view) {
133
+ t5 = view && inHeldOrder(view, heldOrder);
134
+ $[6] = heldOrder;
135
+ $[7] = view;
136
+ $[8] = t5;
137
+ } else t5 = $[8];
138
+ const shown = t5;
139
+ let t6;
140
+ if ($[9] !== shown?.patrollers) {
141
+ t6 = shown?.patrollers.map(_temp).join(" ");
142
+ $[9] = shown?.patrollers;
143
+ $[10] = t6;
144
+ } else t6 = $[10];
145
+ const roster = useMovingRows(t6);
146
+ const [openFileId, setOpenFileId] = React.useState(null);
147
+ const show = useShowReport(inDraft, setOpenFileId);
148
+ if (!shown) return null;
149
+ let t7;
150
+ if ($[11] !== shown.patrollers) {
151
+ t7 = () => setHeldOrder(shown.patrollers.map(_temp2));
152
+ $[11] = shown.patrollers;
153
+ $[12] = t7;
154
+ } else t7 = $[12];
155
+ let t8;
156
+ if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
157
+ t8 = () => setHeldOrder(null);
158
+ $[13] = t8;
159
+ } else t8 = $[13];
160
+ const t9 = locales?.length === 1;
161
+ let t10;
162
+ if ($[14] !== expanded || $[15] !== inDraft || $[16] !== now || $[17] !== show || $[18] !== shown || $[19] !== t9) {
163
+ t10 = /* @__PURE__ */ jsx(PatrolRoster, {
164
+ view: shown,
165
+ now,
166
+ inDraft,
167
+ singleLocale: t9,
168
+ expanded,
169
+ onToggle: toggle,
170
+ onShow: show
171
+ });
172
+ $[14] = expanded;
173
+ $[15] = inDraft;
174
+ $[16] = now;
175
+ $[17] = show;
176
+ $[18] = shown;
177
+ $[19] = t9;
178
+ $[20] = t10;
179
+ } else t10 = $[20];
180
+ let t11;
181
+ if ($[21] !== openFileId) {
182
+ t11 = openFileId && /* @__PURE__ */ jsx(AssetLightbox, {
183
+ open: true,
184
+ onOpenChange: (open) => {
185
+ if (!open) setOpenFileId(null);
186
+ },
187
+ fileId: openFileId
188
+ });
189
+ $[21] = openFileId;
190
+ $[22] = t11;
191
+ } else t11 = $[22];
192
+ let t12;
193
+ if ($[23] !== roster || $[24] !== t10 || $[25] !== t11 || $[26] !== t7) {
194
+ t12 = /* @__PURE__ */ jsxs("div", {
195
+ ref: roster,
196
+ className: "relative",
197
+ onPointerEnter: t7,
198
+ onPointerLeave: t8,
199
+ children: [t10, t11]
200
+ });
201
+ $[23] = roster;
202
+ $[24] = t10;
203
+ $[25] = t11;
204
+ $[26] = t7;
205
+ $[27] = t12;
206
+ } else t12 = $[27];
207
+ return t12;
208
+ }
209
+ function _temp2(t0) {
210
+ const { id: id_1 } = t0;
211
+ return id_1;
212
+ }
213
+ function _temp(t0) {
214
+ const { id: id_0 } = t0;
215
+ return id_0;
216
+ }
217
+ /**
218
+ * Shows a report's subject where it is, in the report's locale, and on Live for a Live report seen
219
+ * from a draft: a page, block or item selected on the Canvas, a record in its collection, or a
220
+ * file, opened over the Canvas.
221
+ */
222
+ function useShowReport(inDraft, openFile) {
223
+ const $ = c(6);
224
+ const showInCanvas = useShowInCanvas();
225
+ const navigate = useNavigate();
226
+ const view = useViewLocale();
227
+ let t0;
228
+ if ($[0] !== inDraft || $[1] !== navigate || $[2] !== openFile || $[3] !== showInCanvas || $[4] !== view) {
229
+ t0 = (report) => {
230
+ const { place } = report;
231
+ if (!place) return;
232
+ const toLive = inDraft && report.live ? { draft: null } : {};
233
+ const tag = reportLocale(report.summary);
234
+ if (place.kind === "page") {
235
+ const { pageId, blockId, itemId } = place;
236
+ showInCanvas({
237
+ pageId,
238
+ locale: tag,
239
+ blockId,
240
+ itemId,
241
+ ...toLive
242
+ });
243
+ return;
244
+ }
245
+ if (place.kind === "record") {
246
+ const locale = view.locales.find((each) => each.tag === tag) ?? view.locale;
247
+ const path = editCollectionItemPath(place.collectionId, place.recordId);
248
+ navigate({
249
+ to: localizedPath(path, locale),
250
+ ...toLive
251
+ });
252
+ return;
253
+ }
254
+ if (inDraft && report.live) navigate({
255
+ to: window.location.href,
256
+ draft: null
257
+ });
258
+ openFile(place.fileId);
259
+ };
260
+ $[0] = inDraft;
261
+ $[1] = navigate;
262
+ $[2] = openFile;
263
+ $[3] = showInCanvas;
264
+ $[4] = view;
265
+ $[5] = t0;
266
+ } else t0 = $[5];
267
+ return t0;
268
+ }
269
+ /**
270
+ * The view with its patrollers in the order held, if any, and its latest content: patrollers the
271
+ * order doesn't hold follow, in the view's order, and those no longer in the view are dropped.
272
+ */
273
+ function inHeldOrder(view, order) {
274
+ if (!order) return view;
275
+ const place = (id) => {
276
+ const index = order.indexOf(id);
277
+ return index === -1 ? order.length : index;
278
+ };
279
+ return {
280
+ ...view,
281
+ patrollers: [...view.patrollers].sort((a, b) => place(a.id) - place(b.id))
282
+ };
283
+ }
284
+ /** How long a patroller takes to move to its new place, in ms. */
285
+ const MOVE_DURATION = 250;
286
+ /** A strong ease-out: the move starts at once and settles gently. */
287
+ const MOVE_EASING = "cubic-bezier(0.23, 1, 0.32, 1)";
288
+ /**
289
+ * Moves each patroller from where it showed to its new place when the order (a key of their ids)
290
+ * changes, rather than jumping there. Without motion for editors who reduce it.
291
+ */
292
+ function useMovingRows(order) {
293
+ const $ = c(2);
294
+ const node = React.useRef(null);
295
+ const last = React.useRef(null);
296
+ let t0;
297
+ if ($[0] !== order) {
298
+ t0 = () => {
299
+ const roster = node.current;
300
+ if (!roster) return;
301
+ const rows = [...roster.querySelectorAll("[data-patroller]")];
302
+ const tops = new Map(rows.map(_temp3));
303
+ const previous = last.current;
304
+ last.current = {
305
+ order,
306
+ tops
307
+ };
308
+ if (!previous || previous.order === order) return;
309
+ if (matchMedia("(prefers-reduced-motion: reduce)").matches) return;
310
+ for (const row_0 of rows) {
311
+ const from = previous.tops.get(row_0.dataset.patroller);
312
+ const to = tops.get(row_0.dataset.patroller);
313
+ if (from === void 0 || from === to) continue;
314
+ row_0.animate([{ transform: `translateY(${from - to}px)` }, { transform: "none" }], {
315
+ duration: MOVE_DURATION,
316
+ easing: MOVE_EASING
317
+ });
318
+ }
319
+ };
320
+ $[0] = order;
321
+ $[1] = t0;
322
+ } else t0 = $[1];
323
+ React.useLayoutEffect(t0);
324
+ return node;
325
+ }
326
+ function _temp3(row) {
327
+ return [row.dataset.patroller, row.offsetTop];
328
+ }
329
+ /** The time now, updated every `intervalMs`. */
330
+ function useNow(intervalMs) {
331
+ const $ = c(3);
332
+ const [now, setNow] = React.useState(_temp4);
333
+ let t0;
334
+ let t1;
335
+ if ($[0] !== intervalMs) {
336
+ t0 = () => {
337
+ const interval = setInterval(() => setNow(Date.now()), intervalMs);
338
+ return () => clearInterval(interval);
339
+ };
340
+ t1 = [intervalMs];
341
+ $[0] = intervalMs;
342
+ $[1] = t0;
343
+ $[2] = t1;
344
+ } else {
345
+ t0 = $[1];
346
+ t1 = $[2];
347
+ }
348
+ React.useEffect(t0, t1);
349
+ return now;
350
+ }
351
+ function _temp4() {
352
+ return Date.now();
353
+ }
354
+ /**
355
+ * A `PatrolView`, in the order it gives: each patroller's avatar, role, and when it last worked,
356
+ * and the reports of the expanded ones. Live's reports carry a tag when a draft is viewed. In a
357
+ * project with a single locale, the Translator invites the editor to add a language instead.
358
+ */
359
+ function PatrolRoster(t0) {
360
+ const $ = c(17);
361
+ const { view, now, inDraft, singleLocale, expanded, onToggle, onShow } = t0;
362
+ let t1;
363
+ if ($[0] !== expanded || $[1] !== inDraft || $[2] !== now || $[3] !== onShow || $[4] !== onToggle || $[5] !== singleLocale || $[6] !== view.patrollers) {
364
+ let t2;
365
+ if ($[8] !== expanded || $[9] !== inDraft || $[10] !== now || $[11] !== onShow || $[12] !== onToggle || $[13] !== singleLocale) {
366
+ t2 = (patroller) => /* @__PURE__ */ jsx(PatrollerRow, {
367
+ patroller,
368
+ now,
369
+ inDraft,
370
+ idle: singleLocale ? singleLocaleSentence(patroller.id) : void 0,
371
+ expanded: expanded.has(patroller.id),
372
+ onToggle: () => onToggle(patroller.id),
373
+ onShow
374
+ }, patroller.id);
375
+ $[8] = expanded;
376
+ $[9] = inDraft;
377
+ $[10] = now;
378
+ $[11] = onShow;
379
+ $[12] = onToggle;
380
+ $[13] = singleLocale;
381
+ $[14] = t2;
382
+ } else t2 = $[14];
383
+ t1 = view.patrollers.map(t2);
384
+ $[0] = expanded;
385
+ $[1] = inDraft;
386
+ $[2] = now;
387
+ $[3] = onShow;
388
+ $[4] = onToggle;
389
+ $[5] = singleLocale;
390
+ $[6] = view.patrollers;
391
+ $[7] = t1;
392
+ } else t1 = $[7];
393
+ let t2;
394
+ if ($[15] !== t1) {
395
+ t2 = /* @__PURE__ */ jsx("ul", {
396
+ className: "flex flex-col py-1",
397
+ children: t1
398
+ });
399
+ $[15] = t1;
400
+ $[16] = t2;
401
+ } else t2 = $[16];
402
+ return t2;
403
+ }
404
+ /**
405
+ * A thin, low-contrast arc travelling around a working patroller's avatar circle, or the whole
406
+ * circle pulsing slowly under reduced motion. The motion lives in `studio.css`.
407
+ */
408
+ function WorkingArc() {
409
+ const $ = c(1);
410
+ let t0;
411
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
412
+ t0 = /* @__PURE__ */ jsx("svg", {
413
+ "data-working-indicator": true,
414
+ "aria-hidden": true,
415
+ viewBox: "0 0 32 32",
416
+ className: "patrol-working text-foreground/30 pointer-events-none absolute inset-0 size-full",
417
+ children: /* @__PURE__ */ jsx("circle", {
418
+ cx: "16",
419
+ cy: "16",
420
+ r: "15.25",
421
+ pathLength: "100",
422
+ fill: "none",
423
+ stroke: "currentColor",
424
+ strokeWidth: "1.5",
425
+ strokeLinecap: "round",
426
+ className: "patrol-working-arc"
427
+ })
428
+ });
429
+ $[0] = t0;
430
+ } else t0 = $[0];
431
+ return t0;
432
+ }
433
+ function PatrollerRow(t0) {
434
+ const $ = c(49);
435
+ const { patroller, now, inDraft, idle, expanded, onToggle, onShow } = t0;
436
+ const reportsId = React.useId();
437
+ const statusId = React.useId();
438
+ let t1;
439
+ if ($[0] !== patroller.id || $[1] !== patroller.working) {
440
+ t1 = patroller.working && workingSentence(patroller.id, patroller.working);
441
+ $[0] = patroller.id;
442
+ $[1] = patroller.working;
443
+ $[2] = t1;
444
+ } else t1 = $[2];
445
+ const working = t1;
446
+ let t2;
447
+ if ($[3] !== idle || $[4] !== now || $[5] !== patroller.lastActivityAt || $[6] !== working) {
448
+ t2 = working ?? idle ?? (patroller.lastActivityAt === null ? "" : timeAgo(patroller.lastActivityAt, now));
449
+ $[3] = idle;
450
+ $[4] = now;
451
+ $[5] = patroller.lastActivityAt;
452
+ $[6] = working;
453
+ $[7] = t2;
454
+ } else t2 = $[7];
455
+ const subtitle = t2;
456
+ const t3 = working ? statusId : void 0;
457
+ const t4 = working ? "working" : "idle";
458
+ let t5;
459
+ if ($[8] !== patroller.id || $[9] !== t4) {
460
+ t5 = /* @__PURE__ */ jsx(Squareling, {
461
+ variant: patroller.id,
462
+ status: t4,
463
+ className: "size-5"
464
+ });
465
+ $[8] = patroller.id;
466
+ $[9] = t4;
467
+ $[10] = t5;
468
+ } else t5 = $[10];
469
+ let t6;
470
+ if ($[11] !== working) {
471
+ t6 = working && /* @__PURE__ */ jsx(WorkingArc, {});
472
+ $[11] = working;
473
+ $[12] = t6;
474
+ } else t6 = $[12];
475
+ let t7;
476
+ if ($[13] !== t5 || $[14] !== t6) {
477
+ t7 = /* @__PURE__ */ jsxs("span", {
478
+ className: "bg-muted relative flex size-8 shrink-0 items-center justify-center rounded-full",
479
+ children: [t5, t6]
480
+ });
481
+ $[13] = t5;
482
+ $[14] = t6;
483
+ $[15] = t7;
484
+ } else t7 = $[15];
485
+ let t8;
486
+ if ($[16] !== patroller.role) {
487
+ t8 = /* @__PURE__ */ jsx("span", {
488
+ "data-patroller-role": true,
489
+ className: "truncate text-sm font-medium",
490
+ children: patroller.role
491
+ });
492
+ $[16] = patroller.role;
493
+ $[17] = t8;
494
+ } else t8 = $[17];
495
+ const t9 = working ? true : void 0;
496
+ let t10;
497
+ if ($[18] !== subtitle || $[19] !== t9) {
498
+ t10 = /* @__PURE__ */ jsx("span", {
499
+ "data-patroller-subtitle": true,
500
+ "aria-hidden": t9,
501
+ className: "text-muted-foreground truncate text-xs",
502
+ children: subtitle
503
+ });
504
+ $[18] = subtitle;
505
+ $[19] = t9;
506
+ $[20] = t10;
507
+ } else t10 = $[20];
508
+ let t11;
509
+ if ($[21] !== t10 || $[22] !== t8) {
510
+ t11 = /* @__PURE__ */ jsxs("span", {
511
+ className: "flex min-w-0 flex-col",
512
+ children: [t8, t10]
513
+ });
514
+ $[21] = t10;
515
+ $[22] = t8;
516
+ $[23] = t11;
517
+ } else t11 = $[23];
518
+ let t12;
519
+ if ($[24] !== expanded || $[25] !== onToggle || $[26] !== reportsId || $[27] !== t11 || $[28] !== t3 || $[29] !== t7) {
520
+ t12 = /* @__PURE__ */ jsxs("button", {
521
+ type: "button",
522
+ "aria-expanded": expanded,
523
+ "aria-controls": reportsId,
524
+ "aria-describedby": t3,
525
+ onClick: onToggle,
526
+ className: "hover:bg-muted/60 focus-visible:ring-ring/50 flex items-center gap-2.5 px-3 py-1.5 text-left outline-none focus-visible:ring-[3px]",
527
+ children: [t7, t11]
528
+ });
529
+ $[24] = expanded;
530
+ $[25] = onToggle;
531
+ $[26] = reportsId;
532
+ $[27] = t11;
533
+ $[28] = t3;
534
+ $[29] = t7;
535
+ $[30] = t12;
536
+ } else t12 = $[30];
537
+ let t13;
538
+ if ($[31] !== working) {
539
+ t13 = working && `Working: ${working.charAt(0).toLowerCase()}${working.slice(1)}`;
540
+ $[31] = working;
541
+ $[32] = t13;
542
+ } else t13 = $[32];
543
+ let t14;
544
+ if ($[33] !== statusId || $[34] !== t13) {
545
+ t14 = /* @__PURE__ */ jsx("span", {
546
+ id: statusId,
547
+ "data-working-status": true,
548
+ role: "status",
549
+ className: "sr-only",
550
+ children: t13
551
+ });
552
+ $[33] = statusId;
553
+ $[34] = t13;
554
+ $[35] = t14;
555
+ } else t14 = $[35];
556
+ let t15;
557
+ if ($[36] !== expanded || $[37] !== inDraft || $[38] !== now || $[39] !== onShow || $[40] !== patroller.more || $[41] !== patroller.reports || $[42] !== reportsId) {
558
+ t15 = expanded && /* @__PURE__ */ jsxs("ul", {
559
+ id: reportsId,
560
+ className: "flex flex-col gap-1 pt-0.5 pr-3 pb-2 pl-[3.375rem] text-xs",
561
+ children: [patroller.reports.map((report) => /* @__PURE__ */ jsxs("li", {
562
+ "data-report": true,
563
+ className: "has-focus-visible:ring-ring/50 has-[button:hover]:bg-muted/60 relative -mx-1.5 flex items-baseline gap-2 rounded-sm px-1.5 has-focus-visible:ring-[3px]",
564
+ children: [
565
+ /* @__PURE__ */ jsx(ReportSentence, {
566
+ report,
567
+ inDraft,
568
+ onShow
569
+ }),
570
+ inDraft && report.live && /* @__PURE__ */ jsx(Badge, {
571
+ variant: "outline",
572
+ size: "sm",
573
+ children: "Live"
574
+ }),
575
+ /* @__PURE__ */ jsx("time", {
576
+ dateTime: new Date(report.at).toISOString(),
577
+ className: "text-muted-foreground shrink-0",
578
+ children: shortTimeAgo(report.at, now)
579
+ })
580
+ ]
581
+ }, report.id)), patroller.more.length > 0 && /* @__PURE__ */ jsx("li", {
582
+ "data-more": true,
583
+ className: "text-muted-foreground",
584
+ children: moreSentence(patroller.more)
585
+ })]
586
+ });
587
+ $[36] = expanded;
588
+ $[37] = inDraft;
589
+ $[38] = now;
590
+ $[39] = onShow;
591
+ $[40] = patroller.more;
592
+ $[41] = patroller.reports;
593
+ $[42] = reportsId;
594
+ $[43] = t15;
595
+ } else t15 = $[43];
596
+ let t16;
597
+ if ($[44] !== patroller.id || $[45] !== t12 || $[46] !== t14 || $[47] !== t15) {
598
+ t16 = /* @__PURE__ */ jsxs("li", {
599
+ "data-patroller": patroller.id,
600
+ className: "flex flex-col",
601
+ children: [
602
+ t12,
603
+ t14,
604
+ t15
605
+ ]
606
+ });
607
+ $[44] = patroller.id;
608
+ $[45] = t12;
609
+ $[46] = t14;
610
+ $[47] = t15;
611
+ $[48] = t16;
612
+ } else t16 = $[48];
613
+ return t16;
614
+ }
615
+ /**
616
+ * A report's sentence: a button showing its subject where it is, over the whole row, named for
617
+ * where it leads. A report without a place is plain text.
618
+ */
619
+ function ReportSentence(t0) {
620
+ const $ = c(15);
621
+ const { report, inDraft, onShow } = t0;
622
+ let t1;
623
+ if ($[0] !== report.outcome || $[1] !== report.summary) {
624
+ t1 = reportSentence(report.summary, report.outcome);
625
+ $[0] = report.outcome;
626
+ $[1] = report.summary;
627
+ $[2] = t1;
628
+ } else t1 = $[2];
629
+ const sentence = t1;
630
+ let t2;
631
+ if ($[3] !== inDraft || $[4] !== report) {
632
+ t2 = whereReportLeads(report, inDraft);
633
+ $[3] = inDraft;
634
+ $[4] = report;
635
+ $[5] = t2;
636
+ } else t2 = $[5];
637
+ const leads = t2;
638
+ if (leads === null) {
639
+ let t3;
640
+ if ($[6] !== sentence) {
641
+ t3 = /* @__PURE__ */ jsx("span", {
642
+ className: "min-w-0 flex-1",
643
+ children: sentence
644
+ });
645
+ $[6] = sentence;
646
+ $[7] = t3;
647
+ } else t3 = $[7];
648
+ return t3;
649
+ }
650
+ const t3 = `${sentence}: ${leads}`;
651
+ let t4;
652
+ if ($[8] !== onShow || $[9] !== report) {
653
+ t4 = () => onShow(report);
654
+ $[8] = onShow;
655
+ $[9] = report;
656
+ $[10] = t4;
657
+ } else t4 = $[10];
658
+ let t5;
659
+ if ($[11] !== sentence || $[12] !== t3 || $[13] !== t4) {
660
+ t5 = /* @__PURE__ */ jsx("button", {
661
+ type: "button",
662
+ "aria-label": t3,
663
+ onClick: t4,
664
+ className: "min-w-0 flex-1 cursor-pointer text-left outline-none after:absolute after:inset-0",
665
+ children: sentence
666
+ });
667
+ $[11] = sentence;
668
+ $[12] = t3;
669
+ $[13] = t4;
670
+ $[14] = t5;
671
+ } else t5 = $[14];
672
+ return t5;
673
+ }
674
+
675
+ //#endregion
676
+ export { Patrol, PatrolRoster, inHeldOrder };