camox 0.72.0 → 0.75.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 (38) hide show
  1. package/dist/core/lib/artifactRuntime.js +4 -2
  2. package/dist/features/preview/components/PageInfo.js +121 -98
  3. package/dist/features/preview/components/SelectionEditor.js +1 -1
  4. package/dist/features/renderer/RendererFrames.js +47 -41
  5. package/dist/features/routes/pageRuntime.js +15 -4
  6. package/dist/features/runtime/ClaimCard.js +192 -0
  7. package/dist/features/runtime/pageApp.js +4 -3
  8. package/dist/features/runtime/runtime.js +26 -5
  9. package/dist/features/runtime/worker.js +2 -2
  10. package/dist/features/runtime/workerRequest.d.ts +6 -0
  11. package/dist/features/runtime/workerRequest.js +13 -1
  12. package/dist/features/runtime/workerRuntime.d.ts +2 -2
  13. package/dist/features/runtime/workerRuntime.js +2 -2
  14. package/dist/features/studio/components/AgentSidebar.js +7 -7
  15. package/dist/features/studio/components/ChatWidget.js +142 -106
  16. package/dist/features/studio/components/ContentFindings.js +127 -230
  17. package/dist/features/studio/components/DeleteDraftDialog.js +112 -0
  18. package/dist/features/studio/components/DraftBar.js +98 -53
  19. package/dist/features/studio/components/DraftsControl.js +203 -190
  20. package/dist/features/studio/components/ReviewDraftDialog.js +670 -0
  21. package/dist/features/studio/components/{Patrol.js → Specialists.js} +291 -330
  22. package/dist/features/studio/components/Squareling.js +5 -5
  23. package/dist/features/studio/components/ThreadComposer.js +106 -127
  24. package/dist/features/studio/components/ThreadMessages.js +246 -71
  25. package/dist/features/studio/findingSentence.js +14 -12
  26. package/dist/features/studio/fixMessage.js +23 -0
  27. package/dist/features/studio/reportSentence.js +1 -1
  28. package/dist/features/studio/{patrollerBio.js → specialistBio.js} +8 -5
  29. package/dist/features/studio/studioStore.js +29 -15
  30. package/dist/features/studio/workingSentence.js +1 -1
  31. package/dist/lib/api-client.js +5 -1
  32. package/dist/lib/queries.js +5 -5
  33. package/dist/lib/studio-reads.d.ts +1 -1
  34. package/dist/lib/studio-reads.js +5 -5
  35. package/dist/package.js +1 -1
  36. package/dist/studio.css +1 -1
  37. package/package.json +4 -4
  38. package/dist/features/studio/components/ReviewDraftSheet.js +0 -1587
@@ -4,6 +4,9 @@ import { ThreadMarkdown } from "./ThreadMarkdown.js";
4
4
  import { c } from "react/compiler-runtime";
5
5
  import * as React from "react";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { Button } from "@camox/ui/button";
8
+ import { X } from "lucide-react";
9
+ import { Dialog, DialogContent, DialogTitle } from "@camox/ui/dialog";
7
10
  import { Avatar, AvatarFallback, AvatarImage } from "@camox/ui/avatar";
8
11
  import { Bubble, BubbleContent } from "@camox/ui/bubble";
9
12
  import { Message, MessageAvatar, MessageContent, MessageHeader } from "@camox/ui/message";
@@ -298,7 +301,7 @@ const PUSH_OUTCOMES = {
298
301
  notPreviewed: "Built, but the preview can’t show it."
299
302
  };
300
303
  function ThreadMessageRow(t0) {
301
- const $ = c(46);
304
+ const $ = c(56);
302
305
  const { message, onOpenDraft } = t0;
303
306
  if (message.from === "draft") {
304
307
  let t1;
@@ -407,95 +410,136 @@ function ThreadMessageRow(t0) {
407
410
  }
408
411
  if (message.from === "connectedAgent") {
409
412
  const { member, client } = message;
413
+ const sender = `${member.name}’s ${client.verified ? client.name : "agent"}`;
414
+ if (message.answer) {
415
+ const t1 = `${sender} added context`;
416
+ const t2 = client.verified ? void 0 : client.name;
417
+ let t3;
418
+ if ($[26] !== message.attachments) {
419
+ t3 = message.attachments && /* @__PURE__ */ jsx(AttachmentThumbnails, { attachments: message.attachments });
420
+ $[26] = message.attachments;
421
+ $[27] = t3;
422
+ } else t3 = $[27];
423
+ let t4;
424
+ if ($[28] !== message.text || $[29] !== t1 || $[30] !== t2 || $[31] !== t3) {
425
+ t4 = /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(CollapsedNote, {
426
+ label: t1,
427
+ hideLabel: "Hide the context",
428
+ title: t2,
429
+ text: message.text,
430
+ children: t3
431
+ }) }) });
432
+ $[28] = message.text;
433
+ $[29] = t1;
434
+ $[30] = t2;
435
+ $[31] = t3;
436
+ $[32] = t4;
437
+ } else t4 = $[32];
438
+ return t4;
439
+ }
410
440
  const t1 = client.verified ? void 0 : client.name;
411
- const t2 = `${member.name}’s ${client.verified ? client.name : "agent"}`;
412
- let t3;
413
- if ($[26] !== t1 || $[27] !== t2) {
414
- t3 = /* @__PURE__ */ jsx(MessageHeader, {
441
+ let t2;
442
+ if ($[33] !== sender || $[34] !== t1) {
443
+ t2 = /* @__PURE__ */ jsx(MessageHeader, {
415
444
  className: "text-muted-foreground text-xs",
416
445
  title: t1,
417
- children: t2
446
+ children: sender
418
447
  });
419
- $[26] = t1;
420
- $[27] = t2;
421
- $[28] = t3;
422
- } else t3 = $[28];
423
- let t4;
424
- if ($[29] !== message.text) {
425
- t4 = /* @__PURE__ */ jsx(Bubble, {
448
+ $[33] = sender;
449
+ $[34] = t1;
450
+ $[35] = t2;
451
+ } else t2 = $[35];
452
+ let t3;
453
+ if ($[36] !== message.text) {
454
+ t3 = /* @__PURE__ */ jsx(Bubble, {
426
455
  variant: "muted",
427
456
  className: "max-w-full",
428
457
  children: /* @__PURE__ */ jsx(BubbleContent, { children: /* @__PURE__ */ jsx(CroppedText, { text: message.text }) })
429
458
  });
430
- $[29] = message.text;
431
- $[30] = t4;
432
- } else t4 = $[30];
459
+ $[36] = message.text;
460
+ $[37] = t3;
461
+ } else t3 = $[37];
462
+ let t4;
463
+ if ($[38] !== message.attachments) {
464
+ t4 = message.attachments && /* @__PURE__ */ jsx(AttachmentThumbnails, { attachments: message.attachments });
465
+ $[38] = message.attachments;
466
+ $[39] = t4;
467
+ } else t4 = $[39];
433
468
  let t5;
434
- if ($[31] !== t3 || $[32] !== t4) {
435
- t5 = /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsxs(MessageContent, { children: [t3, t4] }) });
436
- $[31] = t3;
437
- $[32] = t4;
438
- $[33] = t5;
439
- } else t5 = $[33];
469
+ if ($[40] !== t2 || $[41] !== t3 || $[42] !== t4) {
470
+ t5 = /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsxs(MessageContent, { children: [
471
+ t2,
472
+ t3,
473
+ t4
474
+ ] }) });
475
+ $[40] = t2;
476
+ $[41] = t3;
477
+ $[42] = t4;
478
+ $[43] = t5;
479
+ } else t5 = $[43];
440
480
  return t5;
441
481
  }
442
482
  if (message.from === "brief") {
443
483
  let t1;
444
- if ($[34] !== message.text) {
445
- t1 = /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(BriefNote, { text: message.text }) }) });
446
- $[34] = message.text;
447
- $[35] = t1;
448
- } else t1 = $[35];
484
+ if ($[44] !== message.text) {
485
+ t1 = /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(CollapsedNote, {
486
+ label: "Camox briefed the agent",
487
+ hideLabel: "Hide the brief",
488
+ text: message.text
489
+ }) }) });
490
+ $[44] = message.text;
491
+ $[45] = t1;
492
+ } else t1 = $[45];
449
493
  return t1;
450
494
  }
451
495
  if (message.from === "camox") {
452
496
  let t1;
453
- if ($[36] === Symbol.for("react.memo_cache_sentinel")) {
497
+ if ($[46] === Symbol.for("react.memo_cache_sentinel")) {
454
498
  t1 = /* @__PURE__ */ jsx(MessageHeader, {
455
499
  className: "text-muted-foreground text-xs",
456
500
  children: "Camox"
457
501
  });
458
- $[36] = t1;
459
- } else t1 = $[36];
502
+ $[46] = t1;
503
+ } else t1 = $[46];
460
504
  let t2;
461
- if ($[37] !== message.text) {
505
+ if ($[47] !== message.text) {
462
506
  t2 = /* @__PURE__ */ jsx(Message, { children: /* @__PURE__ */ jsxs(MessageContent, { children: [t1, /* @__PURE__ */ jsx(Bubble, {
463
507
  variant: "muted",
464
508
  className: "max-w-full",
465
509
  children: /* @__PURE__ */ jsx(BubbleContent, { children: /* @__PURE__ */ jsx(CroppedText, { text: message.text }) })
466
510
  })] }) });
467
- $[37] = message.text;
468
- $[38] = t2;
469
- } else t2 = $[38];
511
+ $[47] = message.text;
512
+ $[48] = t2;
513
+ } else t2 = $[48];
470
514
  return t2;
471
515
  }
472
516
  let t1;
473
- if ($[39] !== message.sender) {
517
+ if ($[49] !== message.sender) {
474
518
  t1 = /* @__PURE__ */ jsx(MessageAvatar, { children: /* @__PURE__ */ jsx(SenderAvatar, { sender: message.sender }) });
475
- $[39] = message.sender;
476
- $[40] = t1;
477
- } else t1 = $[40];
519
+ $[49] = message.sender;
520
+ $[50] = t1;
521
+ } else t1 = $[50];
478
522
  let t2;
479
- if ($[41] !== message.text) {
523
+ if ($[51] !== message.text) {
480
524
  t2 = /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(Bubble, {
481
525
  align: "end",
482
526
  variant: "muted",
483
527
  className: "max-w-full",
484
528
  children: /* @__PURE__ */ jsx(BubbleContent, { children: /* @__PURE__ */ jsx(CroppedText, { text: message.text }) })
485
529
  }) });
486
- $[41] = message.text;
487
- $[42] = t2;
488
- } else t2 = $[42];
530
+ $[51] = message.text;
531
+ $[52] = t2;
532
+ } else t2 = $[52];
489
533
  let t3;
490
- if ($[43] !== t1 || $[44] !== t2) {
534
+ if ($[53] !== t1 || $[54] !== t2) {
491
535
  t3 = /* @__PURE__ */ jsxs(Message, {
492
536
  align: "end",
493
537
  children: [t1, t2]
494
538
  });
495
- $[43] = t1;
496
- $[44] = t2;
497
- $[45] = t3;
498
- } else t3 = $[45];
539
+ $[53] = t1;
540
+ $[54] = t2;
541
+ $[55] = t3;
542
+ } else t3 = $[55];
499
543
  return t3;
500
544
  }
501
545
  function _temp(subject, index) {
@@ -586,34 +630,159 @@ function CroppedText(t0) {
586
630
  return t9;
587
631
  }
588
632
  /**
589
- * The Brief the agent on Live handed the request off with, under the editor's messages: a quiet
590
- * line, so the request reads on as they sent it, which shows it once clicked.
633
+ * The Attachments a Connected Agent's message carries, the images Camox's agent saw with it
634
+ * (ADR 0044): a thumbnail of each, which opens it at full size.
591
635
  */
592
- function BriefNote(t0) {
593
- const $ = c(10);
594
- const { text } = t0;
636
+ function AttachmentThumbnails(t0) {
637
+ const $ = c(20);
638
+ const { attachments } = t0;
639
+ const [opened, setOpened] = React.useState();
640
+ let t1;
641
+ if ($[0] !== attachments) {
642
+ let t2;
643
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
644
+ t2 = (attachment) => /* @__PURE__ */ jsx("button", {
645
+ type: "button",
646
+ title: attachment.filename,
647
+ onClick: () => setOpened(attachment),
648
+ className: "checkered focus-visible:ring-ring size-16 overflow-hidden rounded-md border focus-visible:ring-2 focus-visible:outline-none",
649
+ children: /* @__PURE__ */ jsx("img", {
650
+ src: attachment.url,
651
+ alt: attachment.filename,
652
+ className: "size-full object-cover",
653
+ draggable: false
654
+ })
655
+ }, attachment.id);
656
+ $[2] = t2;
657
+ } else t2 = $[2];
658
+ t1 = attachments.map(t2);
659
+ $[0] = attachments;
660
+ $[1] = t1;
661
+ } else t1 = $[1];
662
+ let t2;
663
+ if ($[3] !== t1) {
664
+ t2 = /* @__PURE__ */ jsx("div", {
665
+ "data-thread-attachments": true,
666
+ className: "flex flex-wrap gap-1.5",
667
+ children: t1
668
+ });
669
+ $[3] = t1;
670
+ $[4] = t2;
671
+ } else t2 = $[4];
672
+ const t3 = !!opened;
673
+ let t4;
674
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
675
+ t4 = (open) => !open && setOpened(void 0);
676
+ $[5] = t4;
677
+ } else t4 = $[5];
678
+ const t5 = opened?.filename;
679
+ let t6;
680
+ if ($[6] !== t5) {
681
+ t6 = /* @__PURE__ */ jsx(DialogTitle, {
682
+ className: "sr-only",
683
+ children: t5
684
+ });
685
+ $[6] = t5;
686
+ $[7] = t6;
687
+ } else t6 = $[7];
688
+ let t7;
689
+ if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
690
+ t7 = /* @__PURE__ */ jsxs(Button, {
691
+ type: "button",
692
+ variant: "ghost",
693
+ size: "icon",
694
+ className: "absolute top-2 right-2 z-10",
695
+ onClick: () => setOpened(void 0),
696
+ children: [/* @__PURE__ */ jsx(X, {}), /* @__PURE__ */ jsx("span", {
697
+ className: "sr-only",
698
+ children: "Close"
699
+ })]
700
+ });
701
+ $[8] = t7;
702
+ } else t7 = $[8];
703
+ let t8;
704
+ if ($[9] !== opened) {
705
+ t8 = opened && /* @__PURE__ */ jsx("div", {
706
+ className: "checkered absolute inset-0 flex items-center justify-center overflow-hidden p-6",
707
+ children: /* @__PURE__ */ jsx("img", {
708
+ src: opened.url,
709
+ alt: opened.filename,
710
+ className: "max-h-full max-w-full object-contain shadow-lg",
711
+ draggable: false
712
+ })
713
+ });
714
+ $[9] = opened;
715
+ $[10] = t8;
716
+ } else t8 = $[10];
717
+ let t9;
718
+ if ($[11] !== t6 || $[12] !== t8) {
719
+ t9 = /* @__PURE__ */ jsxs(DialogContent, {
720
+ className: "h-[90vh] max-h-[90vh] w-[90vw] max-w-[90vw] gap-0 overflow-hidden p-0 sm:max-w-[90vw]",
721
+ showCloseButton: false,
722
+ forceOverlay: true,
723
+ children: [
724
+ t6,
725
+ t7,
726
+ t8
727
+ ]
728
+ });
729
+ $[11] = t6;
730
+ $[12] = t8;
731
+ $[13] = t9;
732
+ } else t9 = $[13];
733
+ let t10;
734
+ if ($[14] !== t3 || $[15] !== t9) {
735
+ t10 = /* @__PURE__ */ jsx(Dialog, {
736
+ open: t3,
737
+ onOpenChange: t4,
738
+ children: t9
739
+ });
740
+ $[14] = t3;
741
+ $[15] = t9;
742
+ $[16] = t10;
743
+ } else t10 = $[16];
744
+ let t11;
745
+ if ($[17] !== t10 || $[18] !== t2) {
746
+ t11 = /* @__PURE__ */ jsxs(Fragment, { children: [t2, t10] });
747
+ $[17] = t10;
748
+ $[18] = t2;
749
+ $[19] = t11;
750
+ } else t11 = $[19];
751
+ return t11;
752
+ }
753
+ /**
754
+ * Context the agent was given beside the editors' requests, such as the Brief the agent on Live
755
+ * handed the request off with, or a Connected Agent's answers to its questions: a quiet line,
756
+ * `label`, so the requests read on as they were sent, which shows `text`, then `children`, once
757
+ * clicked.
758
+ */
759
+ function CollapsedNote(t0) {
760
+ const $ = c(12);
761
+ const { label, hideLabel, title, text, children } = t0;
595
762
  const [open, setOpen] = React.useState(false);
596
763
  let t1;
597
764
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
598
765
  t1 = () => setOpen(_temp2);
599
766
  $[0] = t1;
600
767
  } else t1 = $[0];
601
- const t2 = open ? "Hide the brief" : "Camox briefed the agent";
768
+ const t2 = open ? hideLabel : label;
602
769
  let t3;
603
- if ($[1] !== open || $[2] !== t2) {
770
+ if ($[1] !== open || $[2] !== t2 || $[3] !== title) {
604
771
  t3 = /* @__PURE__ */ jsx("button", {
605
772
  type: "button",
606
773
  "aria-expanded": open,
774
+ title,
607
775
  onClick: t1,
608
776
  className: "focus-visible:ring-ring self-start rounded-sm hover:underline focus-visible:ring-2 focus-visible:outline-none",
609
777
  children: t2
610
778
  });
611
779
  $[1] = open;
612
780
  $[2] = t2;
613
- $[3] = t3;
614
- } else t3 = $[3];
781
+ $[3] = title;
782
+ $[4] = t3;
783
+ } else t3 = $[4];
615
784
  let t4;
616
- if ($[4] !== open || $[5] !== text) {
785
+ if ($[5] !== open || $[6] !== text) {
617
786
  t4 = open && /* @__PURE__ */ jsx(Bubble, {
618
787
  variant: "muted",
619
788
  className: "max-w-full",
@@ -622,22 +791,28 @@ function BriefNote(t0) {
622
791
  children: text
623
792
  }) })
624
793
  });
625
- $[4] = open;
626
- $[5] = text;
627
- $[6] = t4;
628
- } else t4 = $[6];
629
- let t5;
630
- if ($[7] !== t3 || $[8] !== t4) {
631
- t5 = /* @__PURE__ */ jsxs("div", {
632
- "data-thread-brief": true,
794
+ $[5] = open;
795
+ $[6] = text;
796
+ $[7] = t4;
797
+ } else t4 = $[7];
798
+ const t5 = open && children;
799
+ let t6;
800
+ if ($[8] !== t3 || $[9] !== t4 || $[10] !== t5) {
801
+ t6 = /* @__PURE__ */ jsxs("div", {
802
+ "data-thread-collapsed": true,
633
803
  className: "text-muted-foreground flex flex-col gap-2 text-xs",
634
- children: [t3, t4]
804
+ children: [
805
+ t3,
806
+ t4,
807
+ t5
808
+ ]
635
809
  });
636
- $[7] = t3;
637
- $[8] = t4;
638
- $[9] = t5;
639
- } else t5 = $[9];
640
- return t5;
810
+ $[8] = t3;
811
+ $[9] = t4;
812
+ $[10] = t5;
813
+ $[11] = t6;
814
+ } else t6 = $[11];
815
+ return t6;
641
816
  }
642
817
  function _temp2(current) {
643
818
  return !current;
@@ -1,14 +1,13 @@
1
- import { localeName } from "./localeOptions.js";
2
- import { findingSentence, findingSentenceInPlace } from "@camox/api-contract/patrol";
1
+ import { fixMessage, whereSentence } from "./fixMessage.js";
2
+ import { findingSentence, findingSentenceInPlace } from "@camox/api-contract/specialists";
3
3
 
4
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". */
5
+ /** What a dismissal dismissed, briefly: "Links to About", or "No meta title". */
8
6
  function dismissedSentence(about) {
9
7
  switch (about.kind) {
10
8
  case "missingPage": return `Links to ${about.page ?? "a missing page"}`;
11
9
  case "brokenLink": return `Links to ${about.url}`;
10
+ case "missingMetaTitle": return "No meta title";
12
11
  }
13
12
  }
14
13
  /** What dismissing a finding says, as its button says it: a broken link's is "This link is fine". */
@@ -22,14 +21,17 @@ function fixLabel(fix) {
22
21
  }
23
22
  }
24
23
  /**
25
- * What "Ask the agent" fills the draft's chat with: who found what, where, and the fix it
26
- * suggests, for the agent to find among the draft's findings and fix.
24
+ * What Fix with agent sends to the chat of what the studio views: who found what, where, and the
25
+ * fix it suggests, for the agent to find among the findings it sees and fix.
27
26
  */
28
- function askAgentMessage(finding, role) {
29
- const where = `(in ${localeName(finding.locale)}, field ${quoted(finding.field)})`;
30
- const fix = finding.fix ? ` Suggested fix: ${fixLabel(finding.fix)}.` : "";
31
- return `${role} finding: ${findingSentence(finding)} ${where}.${fix}`;
27
+ function fixWithAgentMessage(finding, role) {
28
+ return fixMessage({
29
+ from: `${role} finding`,
30
+ what: findingSentence(finding),
31
+ places: [whereSentence(finding)],
32
+ fix: finding.fix ? fixLabel(finding.fix) : void 0
33
+ });
32
34
  }
33
35
 
34
36
  //#endregion
35
- export { askAgentMessage, dismissLabel, dismissedSentence, findingSentence, findingSentenceInPlace, fixLabel };
37
+ export { dismissLabel, dismissedSentence, findingSentence, findingSentenceInPlace, fixLabel, fixWithAgentMessage };
@@ -0,0 +1,23 @@
1
+ import { localeName } from "./localeOptions.js";
2
+
3
+ //#region src/features/studio/fixMessage.ts
4
+ /** Quoted as editors read it. */
5
+ const quoted = (text) => `“${text}”`;
6
+ /** Where something is in content, in a sentence: "Hero on Home, in German, field “image”". */
7
+ function whereSentence({ label, locale, field }) {
8
+ return [
9
+ label,
10
+ locale && `in ${localeName(locale)}`,
11
+ field && `field ${quoted(field)}`
12
+ ].filter(Boolean).join(", ");
13
+ }
14
+ /**
15
+ * The message: "Link checker finding: … (in French, field “body”). Suggested fix: Remove the
16
+ * link." Each of `places` is a `whereSentence`.
17
+ */
18
+ function fixMessage({ from, what, places = [], fix }) {
19
+ return `${from}: ${what}${places.length > 0 ? ` (${places.join("; ")})` : ""}.${fix ? ` Suggested fix: ${fix}.` : ""}`;
20
+ }
21
+
22
+ //#endregion
23
+ export { fixMessage, quoted, whereSentence };
@@ -1,5 +1,5 @@
1
1
  import { localeName } from "./localeOptions.js";
2
- import { reportLocale } from "@camox/api-contract/patrol";
2
+ import { reportLocale } from "@camox/api-contract/specialists";
3
3
 
4
4
  //#region src/features/studio/reportSentence.ts
5
5
  const PHRASES = {
@@ -1,16 +1,16 @@
1
- //#region src/features/studio/patrollerBio.ts
2
- /** What each patroller does, wherever it works. */
1
+ //#region src/features/studio/specialistBio.ts
2
+ /** What each specialist does, wherever it works. */
3
3
  const ABOUT = {
4
4
  seoManager: "Writes what search engines read: each page's SEO, and each file's alt text and filename.",
5
5
  translator: "Translates your website from its default language into every other one, so that each stays complete.",
6
6
  linkChecker: "Looks for links that lead nowhere, to your own pages or elsewhere, and suggests a fix when it can."
7
7
  };
8
- /** What a patroller does, as its profile card introduces it. */
8
+ /** What a specialist does, as its profile card introduces it. */
9
9
  function aboutSentence(id) {
10
10
  return ABOUT[id];
11
11
  }
12
12
  /**
13
- * When a patroller works where the editor is, by its duty there, in a draft or on Live, as its
13
+ * When a specialist works where the editor is, by its duty there, in a draft or on Live, as its
14
14
  * card's tag says it, and whether it runs there at all. In a project with a single locale, the
15
15
  * Translator waits for a second one.
16
16
  */
@@ -23,12 +23,15 @@ function whenTag(id, duty, inDraft, singleLocale) {
23
23
  label: "Runs once you add a language",
24
24
  runs: false
25
25
  };
26
- if (duty === "atPublish") return runs("Runs when you publish");
27
26
  if (duty === "asYouGo") {
28
27
  if (inDraft) return runs("Runs as you edit");
29
28
  if (id === "linkChecker") return runs("Runs every 6 hours");
30
29
  return runs("Runs while a release is translated");
31
30
  }
31
+ if (inDraft) return {
32
+ label: "Runs on Live only",
33
+ runs: false
34
+ };
32
35
  return {
33
36
  label: "Doesn't run on Live",
34
37
  runs: false
@@ -5,12 +5,13 @@ const studioStore = createStore({
5
5
  context: {
6
6
  isCommandPaletteOpen: false,
7
7
  commandPalettePages: [],
8
- isReviewDraftSheetOpen: false,
8
+ isReviewDraftDialogOpen: false,
9
9
  isDeleteDraftDialogOpen: false,
10
10
  localeDialog: null,
11
11
  websiteLocation: null,
12
- agentAsk: null,
13
- agentAsks: 0
12
+ agentMessages: [],
13
+ agentMessagesSent: 0,
14
+ agentWorkingIn: null
14
15
  },
15
16
  on: {
16
17
  openCommandPalette: (context) => {
@@ -45,16 +46,16 @@ const studioStore = createStore({
45
46
  commandPalettePages: context.commandPalettePages.slice(0, -1)
46
47
  };
47
48
  },
48
- openReviewDraftSheet: (context) => {
49
+ openReviewDraftDialog: (context) => {
49
50
  return {
50
51
  ...context,
51
- isReviewDraftSheetOpen: true
52
+ isReviewDraftDialogOpen: true
52
53
  };
53
54
  },
54
- closeReviewDraftSheet: (context) => {
55
+ closeReviewDraftDialog: (context) => {
55
56
  return {
56
57
  ...context,
57
- isReviewDraftSheetOpen: false
58
+ isReviewDraftDialogOpen: false
58
59
  };
59
60
  },
60
61
  openDeleteDraftDialog: (context) => {
@@ -81,21 +82,34 @@ const studioStore = createStore({
81
82
  localeDialog: null
82
83
  };
83
84
  },
84
- askAgent: (context, event) => {
85
+ sendToAgent: (context, event) => {
86
+ const id = context.agentMessagesSent + 1;
85
87
  return {
86
88
  ...context,
87
- isReviewDraftSheetOpen: false,
88
- agentAsk: {
89
+ isReviewDraftDialogOpen: false,
90
+ agentMessages: [...context.agentMessages, {
89
91
  text: event.text,
90
- id: context.agentAsks + 1
91
- },
92
- agentAsks: context.agentAsks + 1
92
+ id
93
+ }],
94
+ agentMessagesSent: id
93
95
  };
94
96
  },
95
- takeAgentAsk: (context) => {
97
+ takeAgentMessage: (context, event) => {
98
+ if (!context.agentMessages.some((message) => message.id === event.id)) return context;
96
99
  return {
97
100
  ...context,
98
- agentAsk: null
101
+ agentMessages: context.agentMessages.filter((message) => message.id !== event.id)
102
+ };
103
+ },
104
+ agentWorking: (context, event) => {
105
+ if (event.working) return {
106
+ ...context,
107
+ agentWorkingIn: event.draftId
108
+ };
109
+ if (context.agentWorkingIn !== event.draftId) return context;
110
+ return {
111
+ ...context,
112
+ agentWorkingIn: null
99
113
  };
100
114
  },
101
115
  rememberWebsiteLocation: (context, event) => {
@@ -35,7 +35,7 @@ function inLocales(locales, preposition) {
35
35
  return listed(parts);
36
36
  }
37
37
  /**
38
- * What a patroller is working on: "Translating hero into Spanish" for one subject, "Translating
38
+ * What a specialist is working on: "Translating hero into Spanish" for one subject, "Translating
39
39
  * 38 items into German and 12 into Spanish" for a burst, counted in each locale.
40
40
  */
41
41
  function workingSentence(id, working) {
@@ -61,6 +61,10 @@ function createStudioClient(apiUrl, draftHeaders) {
61
61
  });
62
62
  return createORPCClient(link);
63
63
  }
64
+ function getApiClient() {
65
+ if (!_client) throw new Error("API client not initialized — call initApiClient first");
66
+ return _client;
67
+ }
64
68
  function getOrpc() {
65
69
  if (!_orpc) throw new Error("API client not initialized — call initApiClient first");
66
70
  return _orpc;
@@ -79,4 +83,4 @@ function getContentWriteHeaders() {
79
83
  }
80
84
 
81
85
  //#endregion
82
- export { getApiUrl, getContentWriteHeaders, getDraftReadClient, getOrpc, initApiClient };
86
+ export { getApiClient, getApiUrl, getContentWriteHeaders, getDraftReadClient, getOrpc, initApiClient };
@@ -51,12 +51,12 @@ const draftMutations = {
51
51
  revertToLive: () => getOrpc().drafts.revertToLive.mutationOptions()
52
52
  };
53
53
  /**
54
- * What members do with a patroller's finding (ADR 0034): apply its fix in the draft viewed, or
54
+ * What members do with a specialist's finding (ADR 0034): apply its fix in the draft viewed, or
55
55
  * dismiss it for the whole project.
56
56
  */
57
- const patrolMutations = {
58
- applyFix: () => getOrpc().patrol.applyFix.mutationOptions(),
59
- dismissFinding: () => getOrpc().patrol.dismissFinding.mutationOptions()
57
+ const specialistMutations = {
58
+ applyFix: () => getOrpc().specialists.applyFix.mutationOptions(),
59
+ dismissFinding: () => getOrpc().specialists.dismissFinding.mutationOptions()
60
60
  };
61
61
  /** Adding and removing locales: content writes, so on Live they start a draft (ADR 0015). */
62
62
  const localeMutations = {
@@ -138,4 +138,4 @@ const fileMutations = {
138
138
  };
139
139
 
140
140
  //#endregion
141
- export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, pageMutations, patrolMutations, projectMutations, repeatableItemMutations };
141
+ export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, pageMutations, projectMutations, repeatableItemMutations, specialistMutations };
@@ -6,7 +6,7 @@ import { ApiClient } from "./api-client.js";
6
6
  import "./queries.js";
7
7
  import "react";
8
8
  import "@camox/api-contract/query-keys";
9
- import "@camox/api-contract/patrol";
9
+ import "@camox/api-contract/specialists";
10
10
  import { QueryClient } from "@tanstack/react-query";
11
11
  //#region src/lib/studio-reads.d.ts
12
12
  type LayoutRead = Awaited<ReturnType<ApiClient["layouts"]["get"]>>;