apostil 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -162,7 +162,6 @@ __apostil_debug.disable()
162
162
  - React 18+
163
163
  - Next.js (App Router)
164
164
  - Tailwind CSS
165
- - lucide-react
166
165
 
167
166
  ## License
168
167
 
package/dist/index.js CHANGED
@@ -417,12 +417,72 @@ function CommentPin({
417
417
 
418
418
  // src/components/comment-thread.tsx
419
419
  import { useEffect as useEffect4, useRef as useRef3, useState as useState4, useCallback as useCallback3 } from "react";
420
- import { Check, Trash2, Undo2 } from "lucide-react";
420
+
421
+ // src/icons.tsx
422
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
423
+ function Send({ className = "w-4 h-4" }) {
424
+ return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
425
+ /* @__PURE__ */ jsx3("path", { d: "m22 2-7 20-4-9-9-4Z" }),
426
+ /* @__PURE__ */ jsx3("path", { d: "M22 2 11 13" })
427
+ ] });
428
+ }
429
+ function MessageSquare({ className = "w-4 h-4" }) {
430
+ return /* @__PURE__ */ jsx3("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx3("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
431
+ }
432
+ function List({ className = "w-4 h-4" }) {
433
+ return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
434
+ /* @__PURE__ */ jsx3("line", { x1: "8", x2: "21", y1: "6", y2: "6" }),
435
+ /* @__PURE__ */ jsx3("line", { x1: "8", x2: "21", y1: "12", y2: "12" }),
436
+ /* @__PURE__ */ jsx3("line", { x1: "8", x2: "21", y1: "18", y2: "18" }),
437
+ /* @__PURE__ */ jsx3("line", { x1: "3", x2: "3.01", y1: "6", y2: "6" }),
438
+ /* @__PURE__ */ jsx3("line", { x1: "3", x2: "3.01", y1: "12", y2: "12" }),
439
+ /* @__PURE__ */ jsx3("line", { x1: "3", x2: "3.01", y1: "18", y2: "18" })
440
+ ] });
441
+ }
442
+ function X({ className = "w-4 h-4" }) {
443
+ return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
444
+ /* @__PURE__ */ jsx3("path", { d: "M18 6 6 18" }),
445
+ /* @__PURE__ */ jsx3("path", { d: "m6 6 12 12" })
446
+ ] });
447
+ }
448
+ function Check({ className = "w-4 h-4" }) {
449
+ return /* @__PURE__ */ jsx3("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx3("path", { d: "M20 6 9 17l-5-5" }) });
450
+ }
451
+ function Undo2({ className = "w-4 h-4" }) {
452
+ return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
453
+ /* @__PURE__ */ jsx3("path", { d: "M9 14 4 9l5-5" }),
454
+ /* @__PURE__ */ jsx3("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" })
455
+ ] });
456
+ }
457
+ function Globe({ className = "w-4 h-4" }) {
458
+ return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
459
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "10" }),
460
+ /* @__PURE__ */ jsx3("path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" }),
461
+ /* @__PURE__ */ jsx3("path", { d: "M2 12h20" })
462
+ ] });
463
+ }
464
+ function FileText({ className = "w-4 h-4" }) {
465
+ return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
466
+ /* @__PURE__ */ jsx3("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
467
+ /* @__PURE__ */ jsx3("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
468
+ /* @__PURE__ */ jsx3("path", { d: "M10 9H8" }),
469
+ /* @__PURE__ */ jsx3("path", { d: "M16 13H8" }),
470
+ /* @__PURE__ */ jsx3("path", { d: "M16 17H8" })
471
+ ] });
472
+ }
473
+ function Trash2({ className = "w-4 h-4" }) {
474
+ return /* @__PURE__ */ jsxs2("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
475
+ /* @__PURE__ */ jsx3("path", { d: "M3 6h18" }),
476
+ /* @__PURE__ */ jsx3("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
477
+ /* @__PURE__ */ jsx3("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
478
+ /* @__PURE__ */ jsx3("line", { x1: "10", x2: "10", y1: "11", y2: "17" }),
479
+ /* @__PURE__ */ jsx3("line", { x1: "14", x2: "14", y1: "11", y2: "17" })
480
+ ] });
481
+ }
421
482
 
422
483
  // src/components/comment-composer.tsx
423
484
  import { useState as useState3, useRef as useRef2, useEffect as useEffect3 } from "react";
424
- import { Send } from "lucide-react";
425
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
485
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
426
486
  function CommentComposer({
427
487
  onSubmit,
428
488
  placeholder = "Add a comment...",
@@ -441,8 +501,8 @@ function CommentComposer({
441
501
  onSubmit(trimmed);
442
502
  setValue("");
443
503
  };
444
- return /* @__PURE__ */ jsxs2("div", { className: "flex gap-2 items-end", children: [
445
- /* @__PURE__ */ jsx3(
504
+ return /* @__PURE__ */ jsxs3("div", { className: "flex gap-2 items-end", children: [
505
+ /* @__PURE__ */ jsx4(
446
506
  "textarea",
447
507
  {
448
508
  ref: inputRef,
@@ -459,20 +519,20 @@ function CommentComposer({
459
519
  className: "flex-1 resize-none rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm\n placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-neutral-300\n min-h-[36px] max-h-[120px]"
460
520
  }
461
521
  ),
462
- /* @__PURE__ */ jsx3(
522
+ /* @__PURE__ */ jsx4(
463
523
  "button",
464
524
  {
465
525
  onClick: handleSubmit,
466
526
  disabled: !value.trim(),
467
527
  className: "flex items-center justify-center w-8 h-8 rounded-lg\n bg-neutral-900 text-white disabled:opacity-30\n hover:bg-neutral-700 transition-colors shrink-0",
468
- children: /* @__PURE__ */ jsx3(Send, { className: "w-3.5 h-3.5" })
528
+ children: /* @__PURE__ */ jsx4(Send, { className: "w-3.5 h-3.5" })
469
529
  }
470
530
  )
471
531
  ] });
472
532
  }
473
533
 
474
534
  // src/components/comment-thread.tsx
475
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
535
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
476
536
  function timeAgo(iso) {
477
537
  const diff = Date.now() - new Date(iso).getTime();
478
538
  const mins = Math.floor(diff / 6e4);
@@ -518,48 +578,48 @@ function ApostilThreadPopover({
518
578
  };
519
579
  }, [isOpen, setActiveThreadId]);
520
580
  if (!isOpen || !pos) return null;
521
- return /* @__PURE__ */ jsx4(
581
+ return /* @__PURE__ */ jsx5(
522
582
  "div",
523
583
  {
524
584
  ref,
525
585
  className: "absolute z-[70] ml-5 -mt-3",
526
586
  style: { left: pos.left, top: pos.top },
527
587
  onClick: (e) => e.stopPropagation(),
528
- children: /* @__PURE__ */ jsxs3("div", { className: "w-80 bg-white rounded-xl shadow-2xl border border-neutral-200 overflow-hidden", children: [
529
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between px-4 py-2.5 border-b border-neutral-100 bg-neutral-50", children: [
530
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
531
- /* @__PURE__ */ jsxs3("span", { className: "text-xs font-medium text-neutral-500", children: [
588
+ children: /* @__PURE__ */ jsxs4("div", { className: "w-80 bg-white rounded-xl shadow-2xl border border-neutral-200 overflow-hidden", children: [
589
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between px-4 py-2.5 border-b border-neutral-100 bg-neutral-50", children: [
590
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
591
+ /* @__PURE__ */ jsxs4("span", { className: "text-xs font-medium text-neutral-500", children: [
532
592
  thread.comments.length,
533
593
  " ",
534
594
  thread.comments.length === 1 ? "comment" : "comments"
535
595
  ] }),
536
- thread.targetLabel && /* @__PURE__ */ jsx4("span", { className: "text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium", children: thread.targetLabel }),
537
- thread.resolved && /* @__PURE__ */ jsx4("span", { className: "text-[10px] text-emerald-600 font-medium", children: "Resolved" })
596
+ thread.targetLabel && /* @__PURE__ */ jsx5("span", { className: "text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium", children: thread.targetLabel }),
597
+ thread.resolved && /* @__PURE__ */ jsx5("span", { className: "text-[10px] text-emerald-600 font-medium", children: "Resolved" })
538
598
  ] }),
539
- /* @__PURE__ */ jsxs3("div", { className: "flex gap-1", children: [
540
- /* @__PURE__ */ jsx4(
599
+ /* @__PURE__ */ jsxs4("div", { className: "flex gap-1", children: [
600
+ /* @__PURE__ */ jsx5(
541
601
  "button",
542
602
  {
543
603
  onClick: () => resolveThread(thread.id),
544
604
  className: "p-1 rounded hover:bg-neutral-200 transition-colors",
545
605
  title: thread.resolved ? "Reopen" : "Resolve",
546
- children: thread.resolved ? /* @__PURE__ */ jsx4(Undo2, { className: "w-3.5 h-3.5 text-neutral-500" }) : /* @__PURE__ */ jsx4(Check, { className: "w-3.5 h-3.5 text-emerald-600" })
606
+ children: thread.resolved ? /* @__PURE__ */ jsx5(Undo2, { className: "w-3.5 h-3.5 text-neutral-500" }) : /* @__PURE__ */ jsx5(Check, { className: "w-3.5 h-3.5 text-emerald-600" })
547
607
  }
548
608
  ),
549
- /* @__PURE__ */ jsx4(
609
+ /* @__PURE__ */ jsx5(
550
610
  "button",
551
611
  {
552
612
  onClick: () => deleteThread(thread.id),
553
613
  className: "p-1 rounded hover:bg-red-50 transition-colors",
554
614
  title: "Delete thread",
555
- children: /* @__PURE__ */ jsx4(Trash2, { className: "w-3.5 h-3.5 text-neutral-400 hover:text-red-500" })
615
+ children: /* @__PURE__ */ jsx5(Trash2, { className: "w-3.5 h-3.5 text-neutral-400 hover:text-red-500" })
556
616
  }
557
617
  )
558
618
  ] })
559
619
  ] }),
560
- /* @__PURE__ */ jsx4("div", { className: "max-h-64 overflow-y-auto", children: thread.comments.map((comment) => /* @__PURE__ */ jsxs3("div", { className: "px-4 py-3 border-b border-neutral-50 last:border-0", children: [
561
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 mb-1", children: [
562
- /* @__PURE__ */ jsx4(
620
+ /* @__PURE__ */ jsx5("div", { className: "max-h-64 overflow-y-auto", children: thread.comments.map((comment) => /* @__PURE__ */ jsxs4("div", { className: "px-4 py-3 border-b border-neutral-50 last:border-0", children: [
621
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 mb-1", children: [
622
+ /* @__PURE__ */ jsx5(
563
623
  "div",
564
624
  {
565
625
  className: "w-5 h-5 rounded-full flex items-center justify-center text-white text-[10px] font-semibold shrink-0",
@@ -567,12 +627,12 @@ function ApostilThreadPopover({
567
627
  children: comment.author.name[0]?.toUpperCase()
568
628
  }
569
629
  ),
570
- /* @__PURE__ */ jsx4("span", { className: "text-xs font-medium text-neutral-800", children: comment.author.name }),
571
- /* @__PURE__ */ jsx4("span", { className: "text-[10px] text-neutral-400 ml-auto", children: timeAgo(comment.createdAt) })
630
+ /* @__PURE__ */ jsx5("span", { className: "text-xs font-medium text-neutral-800", children: comment.author.name }),
631
+ /* @__PURE__ */ jsx5("span", { className: "text-[10px] text-neutral-400 ml-auto", children: timeAgo(comment.createdAt) })
572
632
  ] }),
573
- /* @__PURE__ */ jsx4("p", { className: "text-sm text-neutral-700 leading-relaxed pl-7", children: comment.body })
633
+ /* @__PURE__ */ jsx5("p", { className: "text-sm text-neutral-700 leading-relaxed pl-7", children: comment.body })
574
634
  ] }, comment.id)) }),
575
- user && !thread.resolved && /* @__PURE__ */ jsx4("div", { className: "px-3 py-2.5 border-t border-neutral-100 bg-neutral-50/50", children: /* @__PURE__ */ jsx4(
635
+ user && !thread.resolved && /* @__PURE__ */ jsx5("div", { className: "px-3 py-2.5 border-t border-neutral-100 bg-neutral-50/50", children: /* @__PURE__ */ jsx5(
576
636
  CommentComposer,
577
637
  {
578
638
  onSubmit: (body) => addReply(thread.id, body),
@@ -587,7 +647,7 @@ function ApostilThreadPopover({
587
647
 
588
648
  // src/components/user-prompt.tsx
589
649
  import { useState as useState5 } from "react";
590
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
650
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
591
651
  function UserPrompt() {
592
652
  const { user, setUser, commentMode } = useApostil();
593
653
  const [name, setName] = useState5("");
@@ -597,10 +657,10 @@ function UserPrompt() {
597
657
  if (!trimmed) return;
598
658
  setUser(trimmed);
599
659
  };
600
- return /* @__PURE__ */ jsx5("div", { className: "absolute inset-0 z-[80] flex items-center justify-center bg-black/20 backdrop-blur-[2px]", children: /* @__PURE__ */ jsxs4("div", { className: "bg-white rounded-xl shadow-2xl border border-neutral-200 p-6 w-80", children: [
601
- /* @__PURE__ */ jsx5("h3", { className: "text-sm font-semibold text-neutral-900 mb-1", children: "What's your name?" }),
602
- /* @__PURE__ */ jsx5("p", { className: "text-xs text-neutral-500 mb-4", children: "This will be shown with your comments." }),
603
- /* @__PURE__ */ jsx5(
660
+ return /* @__PURE__ */ jsx6("div", { className: "absolute inset-0 z-[80] flex items-center justify-center bg-black/20 backdrop-blur-[2px]", children: /* @__PURE__ */ jsxs5("div", { className: "bg-white rounded-xl shadow-2xl border border-neutral-200 p-6 w-80", children: [
661
+ /* @__PURE__ */ jsx6("h3", { className: "text-sm font-semibold text-neutral-900 mb-1", children: "What's your name?" }),
662
+ /* @__PURE__ */ jsx6("p", { className: "text-xs text-neutral-500 mb-4", children: "This will be shown with your comments." }),
663
+ /* @__PURE__ */ jsx6(
604
664
  "input",
605
665
  {
606
666
  value: name,
@@ -613,7 +673,7 @@ function UserPrompt() {
613
673
  className: "w-full rounded-lg border border-neutral-200 px-3 py-2 text-sm\n placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-neutral-300 mb-3"
614
674
  }
615
675
  ),
616
- /* @__PURE__ */ jsx5(
676
+ /* @__PURE__ */ jsx6(
617
677
  "button",
618
678
  {
619
679
  onClick: handleSubmit,
@@ -626,7 +686,7 @@ function UserPrompt() {
626
686
  }
627
687
 
628
688
  // src/components/comment-overlay.tsx
629
- import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
689
+ import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
630
690
  var cachedHighZ = 0;
631
691
  var cacheTimestamp = 0;
632
692
  function getHighestZIndex() {
@@ -879,9 +939,9 @@ function CommentOverlay() {
879
939
  if (a.resolved !== b.resolved) return a.resolved ? 1 : -1;
880
940
  return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
881
941
  });
882
- return /* @__PURE__ */ jsxs5(Fragment, { children: [
883
- /* @__PURE__ */ jsx6(UserPrompt, {}),
884
- /* @__PURE__ */ jsxs5(
942
+ return /* @__PURE__ */ jsxs6(Fragment, { children: [
943
+ /* @__PURE__ */ jsx7(UserPrompt, {}),
944
+ /* @__PURE__ */ jsxs6(
885
945
  "div",
886
946
  {
887
947
  ref: overlayRef,
@@ -889,11 +949,11 @@ function CommentOverlay() {
889
949
  style: { zIndex: commentMode ? getHighestZIndex() + 10 : 55 },
890
950
  onMouseDown: handleClick,
891
951
  children: [
892
- sortedThreads.map((thread, i) => /* @__PURE__ */ jsxs5("div", { className: "pointer-events-auto", children: [
893
- /* @__PURE__ */ jsx6(CommentPin, { thread, index: i, overlayRef }),
894
- /* @__PURE__ */ jsx6(ApostilThreadPopover, { thread, overlayRef })
952
+ sortedThreads.map((thread, i) => /* @__PURE__ */ jsxs6("div", { className: "pointer-events-auto", children: [
953
+ /* @__PURE__ */ jsx7(CommentPin, { thread, index: i, overlayRef }),
954
+ /* @__PURE__ */ jsx7(ApostilThreadPopover, { thread, overlayRef })
895
955
  ] }, thread.id)),
896
- pendingPin && pendingPixel && user && /* @__PURE__ */ jsxs5(
956
+ pendingPin && pendingPixel && user && /* @__PURE__ */ jsxs6(
897
957
  "div",
898
958
  {
899
959
  className: "absolute z-[70] pointer-events-auto",
@@ -904,7 +964,7 @@ function CommentOverlay() {
904
964
  onMouseDown: (e) => e.stopPropagation(),
905
965
  onClick: (e) => e.stopPropagation(),
906
966
  children: [
907
- /* @__PURE__ */ jsx6(
967
+ /* @__PURE__ */ jsx7(
908
968
  "div",
909
969
  {
910
970
  className: "absolute -translate-x-1/2 -translate-y-1/2 w-7 h-7 rounded-full\n flex items-center justify-center text-white text-xs font-semibold\n shadow-lg ring-2 ring-white ring-offset-2 animate-bounce",
@@ -912,12 +972,12 @@ function CommentOverlay() {
912
972
  children: "+"
913
973
  }
914
974
  ),
915
- /* @__PURE__ */ jsx6("div", { className: "absolute ml-5 -mt-3 w-72", children: /* @__PURE__ */ jsxs5("div", { className: "bg-white rounded-xl shadow-2xl border border-neutral-200 p-3", children: [
916
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 mb-2", children: [
917
- /* @__PURE__ */ jsx6("p", { className: "text-xs text-neutral-500", children: "New comment" }),
918
- pendingPin.targetLabel && /* @__PURE__ */ jsx6("span", { className: "text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium", children: pendingPin.targetLabel })
975
+ /* @__PURE__ */ jsx7("div", { className: "absolute ml-5 -mt-3 w-72", children: /* @__PURE__ */ jsxs6("div", { className: "bg-white rounded-xl shadow-2xl border border-neutral-200 p-3", children: [
976
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 mb-2", children: [
977
+ /* @__PURE__ */ jsx7("p", { className: "text-xs text-neutral-500", children: "New comment" }),
978
+ pendingPin.targetLabel && /* @__PURE__ */ jsx7("span", { className: "text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium", children: pendingPin.targetLabel })
919
979
  ] }),
920
- /* @__PURE__ */ jsx6(
980
+ /* @__PURE__ */ jsx7(
921
981
  CommentComposer,
922
982
  {
923
983
  onSubmit: handleNewComment,
@@ -925,7 +985,7 @@ function CommentOverlay() {
925
985
  autoFocus: true
926
986
  }
927
987
  ),
928
- /* @__PURE__ */ jsx6(
988
+ /* @__PURE__ */ jsx7(
929
989
  "button",
930
990
  {
931
991
  onClick: cancelPending,
@@ -940,13 +1000,12 @@ function CommentOverlay() {
940
1000
  ]
941
1001
  }
942
1002
  ),
943
- commentMode && !pendingPin && /* @__PURE__ */ jsx6("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 z-[60] pointer-events-none", children: /* @__PURE__ */ jsx6("div", { className: "bg-neutral-900/80 text-white text-sm px-4 py-2 rounded-full backdrop-blur-sm", children: "Click anywhere to add a comment" }) })
1003
+ commentMode && !pendingPin && /* @__PURE__ */ jsx7("div", { className: "absolute bottom-6 left-1/2 -translate-x-1/2 z-[60] pointer-events-none", children: /* @__PURE__ */ jsx7("div", { className: "bg-neutral-900/80 text-white text-sm px-4 py-2 rounded-full backdrop-blur-sm", children: "Click anywhere to add a comment" }) })
944
1004
  ] });
945
1005
  }
946
1006
 
947
1007
  // src/components/comment-toggle.tsx
948
- import { MessageSquare, List, X } from "lucide-react";
949
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1008
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
950
1009
  function CommentToggle() {
951
1010
  const {
952
1011
  commentMode,
@@ -956,8 +1015,8 @@ function CommentToggle() {
956
1015
  unresolvedCount,
957
1016
  setActiveThreadId
958
1017
  } = useApostil();
959
- return /* @__PURE__ */ jsxs6("div", { className: "absolute bottom-5 right-5 z-[65] flex flex-col gap-2 items-end", children: [
960
- /* @__PURE__ */ jsx7(
1018
+ return /* @__PURE__ */ jsxs7("div", { className: "absolute bottom-5 right-5 z-[65] flex flex-col gap-2 items-end", children: [
1019
+ /* @__PURE__ */ jsx8(
961
1020
  "button",
962
1021
  {
963
1022
  onClick: () => setSidebarOpen(!sidebarOpen),
@@ -965,10 +1024,10 @@ function CommentToggle() {
965
1024
  transition-all duration-200 hover:scale-105
966
1025
  ${sidebarOpen ? "bg-neutral-900 text-white" : "bg-white text-neutral-600 hover:bg-neutral-50 border border-neutral-200"}`,
967
1026
  title: "Toggle comment list",
968
- children: /* @__PURE__ */ jsx7(List, { className: "w-4 h-4" })
1027
+ children: /* @__PURE__ */ jsx8(List, { className: "w-4 h-4" })
969
1028
  }
970
1029
  ),
971
- /* @__PURE__ */ jsxs6(
1030
+ /* @__PURE__ */ jsxs7(
972
1031
  "button",
973
1032
  {
974
1033
  onClick: () => {
@@ -984,8 +1043,8 @@ function CommentToggle() {
984
1043
  ${commentMode ? "bg-neutral-900 text-white ring-2 ring-neutral-400" : "bg-white text-neutral-700 hover:bg-neutral-50 border border-neutral-200"}`,
985
1044
  title: commentMode ? "Exit comment mode" : "Add comment",
986
1045
  children: [
987
- commentMode ? /* @__PURE__ */ jsx7(X, { className: "w-5 h-5" }) : /* @__PURE__ */ jsx7(MessageSquare, { className: "w-5 h-5" }),
988
- unresolvedCount > 0 && !commentMode && /* @__PURE__ */ jsx7("span", { className: "absolute -top-1 -right-1 min-w-[18px] h-[18px] rounded-full\n bg-red-500 text-white text-[10px] font-semibold\n flex items-center justify-center px-1", children: unresolvedCount })
1046
+ commentMode ? /* @__PURE__ */ jsx8(X, { className: "w-5 h-5" }) : /* @__PURE__ */ jsx8(MessageSquare, { className: "w-5 h-5" }),
1047
+ unresolvedCount > 0 && !commentMode && /* @__PURE__ */ jsx8("span", { className: "absolute -top-1 -right-1 min-w-[18px] h-[18px] rounded-full\n bg-red-500 text-white text-[10px] font-semibold\n flex items-center justify-center px-1", children: unresolvedCount })
989
1048
  ]
990
1049
  }
991
1050
  )
@@ -994,8 +1053,7 @@ function CommentToggle() {
994
1053
 
995
1054
  // src/components/comment-sidebar.tsx
996
1055
  import { useState as useState7, useEffect as useEffect6 } from "react";
997
- import { X as X2, Check as Check2, Undo2 as Undo22, MessageSquare as MessageSquare2, Globe, FileText } from "lucide-react";
998
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1056
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
999
1057
  function timeAgo2(iso) {
1000
1058
  const diff = Date.now() - new Date(iso).getTime();
1001
1059
  const mins = Math.floor(diff / 6e4);
@@ -1039,47 +1097,47 @@ function CommentSidebar() {
1039
1097
  if (!sidebarOpen) return null;
1040
1098
  const openThreads = threads.filter((t) => !t.resolved);
1041
1099
  const resolvedThreads = threads.filter((t) => t.resolved);
1042
- return /* @__PURE__ */ jsxs7("div", { className: "absolute top-0 right-0 bottom-0 w-80 z-[75] bg-white border-l border-neutral-200 shadow-xl flex flex-col", children: [
1043
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between px-4 py-3 border-b border-neutral-100", children: [
1044
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
1045
- /* @__PURE__ */ jsx8(MessageSquare2, { className: "w-4 h-4 text-neutral-500" }),
1046
- /* @__PURE__ */ jsx8("span", { className: "text-sm font-semibold text-neutral-900", children: "Comments" })
1100
+ return /* @__PURE__ */ jsxs8("div", { className: "absolute top-0 right-0 bottom-0 w-80 z-[75] bg-white border-l border-neutral-200 shadow-xl flex flex-col", children: [
1101
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between px-4 py-3 border-b border-neutral-100", children: [
1102
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1103
+ /* @__PURE__ */ jsx9(MessageSquare, { className: "w-4 h-4 text-neutral-500" }),
1104
+ /* @__PURE__ */ jsx9("span", { className: "text-sm font-semibold text-neutral-900", children: "Comments" })
1047
1105
  ] }),
1048
- /* @__PURE__ */ jsx8(
1106
+ /* @__PURE__ */ jsx9(
1049
1107
  "button",
1050
1108
  {
1051
1109
  onClick: () => setSidebarOpen(false),
1052
1110
  className: "p-1 rounded hover:bg-neutral-100 transition-colors",
1053
- children: /* @__PURE__ */ jsx8(X2, { className: "w-4 h-4 text-neutral-500" })
1111
+ children: /* @__PURE__ */ jsx9(X, { className: "w-4 h-4 text-neutral-500" })
1054
1112
  }
1055
1113
  )
1056
1114
  ] }),
1057
- /* @__PURE__ */ jsxs7("div", { className: "flex border-b border-neutral-100", children: [
1058
- /* @__PURE__ */ jsxs7(
1115
+ /* @__PURE__ */ jsxs8("div", { className: "flex border-b border-neutral-100", children: [
1116
+ /* @__PURE__ */ jsxs8(
1059
1117
  "button",
1060
1118
  {
1061
1119
  onClick: () => setTab("page"),
1062
1120
  className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "page" ? "text-neutral-900 border-b-2 border-neutral-900" : "text-neutral-400 hover:text-neutral-600"}`,
1063
1121
  children: [
1064
- /* @__PURE__ */ jsx8(FileText, { className: "w-3 h-3" }),
1122
+ /* @__PURE__ */ jsx9(FileText, { className: "w-3 h-3" }),
1065
1123
  "This Page",
1066
- openThreads.length > 0 && /* @__PURE__ */ jsx8("span", { className: "text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full", children: openThreads.length })
1124
+ openThreads.length > 0 && /* @__PURE__ */ jsx9("span", { className: "text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full", children: openThreads.length })
1067
1125
  ]
1068
1126
  }
1069
1127
  ),
1070
- /* @__PURE__ */ jsxs7(
1128
+ /* @__PURE__ */ jsxs8(
1071
1129
  "button",
1072
1130
  {
1073
1131
  onClick: () => setTab("all"),
1074
1132
  className: `flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${tab === "all" ? "text-neutral-900 border-b-2 border-neutral-900" : "text-neutral-400 hover:text-neutral-600"}`,
1075
1133
  children: [
1076
- /* @__PURE__ */ jsx8(Globe, { className: "w-3 h-3" }),
1134
+ /* @__PURE__ */ jsx9(Globe, { className: "w-3 h-3" }),
1077
1135
  "All Pages"
1078
1136
  ]
1079
1137
  }
1080
1138
  )
1081
1139
  ] }),
1082
- /* @__PURE__ */ jsx8("div", { className: "flex-1 overflow-y-auto", children: tab === "page" ? /* @__PURE__ */ jsx8(
1140
+ /* @__PURE__ */ jsx9("div", { className: "flex-1 overflow-y-auto", children: tab === "page" ? /* @__PURE__ */ jsx9(
1083
1141
  PageThreads,
1084
1142
  {
1085
1143
  threads,
@@ -1088,7 +1146,7 @@ function CommentSidebar() {
1088
1146
  onSelect: setActiveThreadId,
1089
1147
  onResolve: resolveThread
1090
1148
  }
1091
- ) : /* @__PURE__ */ jsx8(
1149
+ ) : /* @__PURE__ */ jsx9(
1092
1150
  AllPagesView,
1093
1151
  {
1094
1152
  pages: allPages,
@@ -1105,16 +1163,16 @@ function PageThreads({
1105
1163
  onResolve
1106
1164
  }) {
1107
1165
  if (threads.length === 0) {
1108
- return /* @__PURE__ */ jsx8("div", { className: "p-6 text-center text-sm text-neutral-400", children: "No comments on this page." });
1166
+ return /* @__PURE__ */ jsx9("div", { className: "p-6 text-center text-sm text-neutral-400", children: "No comments on this page." });
1109
1167
  }
1110
- return /* @__PURE__ */ jsxs7(Fragment2, { children: [
1111
- openThreads.length > 0 && /* @__PURE__ */ jsxs7("div", { children: [
1112
- /* @__PURE__ */ jsxs7("div", { className: "px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider", children: [
1168
+ return /* @__PURE__ */ jsxs8(Fragment2, { children: [
1169
+ openThreads.length > 0 && /* @__PURE__ */ jsxs8("div", { children: [
1170
+ /* @__PURE__ */ jsxs8("div", { className: "px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider", children: [
1113
1171
  "Open (",
1114
1172
  openThreads.length,
1115
1173
  ")"
1116
1174
  ] }),
1117
- openThreads.map((thread) => /* @__PURE__ */ jsx8(
1175
+ openThreads.map((thread) => /* @__PURE__ */ jsx9(
1118
1176
  ThreadItem,
1119
1177
  {
1120
1178
  thread,
@@ -1124,13 +1182,13 @@ function PageThreads({
1124
1182
  thread.id
1125
1183
  ))
1126
1184
  ] }),
1127
- resolvedThreads.length > 0 && /* @__PURE__ */ jsxs7("div", { children: [
1128
- /* @__PURE__ */ jsxs7("div", { className: "px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider", children: [
1185
+ resolvedThreads.length > 0 && /* @__PURE__ */ jsxs8("div", { children: [
1186
+ /* @__PURE__ */ jsxs8("div", { className: "px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider", children: [
1129
1187
  "Resolved (",
1130
1188
  resolvedThreads.length,
1131
1189
  ")"
1132
1190
  ] }),
1133
- resolvedThreads.map((thread) => /* @__PURE__ */ jsx8(
1191
+ resolvedThreads.map((thread) => /* @__PURE__ */ jsx9(
1134
1192
  ThreadItem,
1135
1193
  {
1136
1194
  thread,
@@ -1148,14 +1206,14 @@ function AllPagesView({
1148
1206
  loading
1149
1207
  }) {
1150
1208
  if (loading) {
1151
- return /* @__PURE__ */ jsx8("div", { className: "p-6 text-center text-sm text-neutral-400", children: "Loading..." });
1209
+ return /* @__PURE__ */ jsx9("div", { className: "p-6 text-center text-sm text-neutral-400", children: "Loading..." });
1152
1210
  }
1153
1211
  if (pages.length === 0) {
1154
- return /* @__PURE__ */ jsx8("div", { className: "p-6 text-center text-sm text-neutral-400", children: "No comments in this project yet." });
1212
+ return /* @__PURE__ */ jsx9("div", { className: "p-6 text-center text-sm text-neutral-400", children: "No comments in this project yet." });
1155
1213
  }
1156
1214
  const totalOpen = pages.reduce((s, p) => s + p.threads.filter((t) => !t.resolved).length, 0);
1157
- return /* @__PURE__ */ jsxs7(Fragment2, { children: [
1158
- totalOpen > 0 && /* @__PURE__ */ jsxs7("div", { className: "px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider", children: [
1215
+ return /* @__PURE__ */ jsxs8(Fragment2, { children: [
1216
+ totalOpen > 0 && /* @__PURE__ */ jsxs8("div", { className: "px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider", children: [
1159
1217
  totalOpen,
1160
1218
  " open across ",
1161
1219
  pages.length,
@@ -1165,19 +1223,19 @@ function AllPagesView({
1165
1223
  const open = page.threads.filter((t) => !t.resolved);
1166
1224
  const resolved = page.threads.filter((t) => t.resolved);
1167
1225
  const displayName = pageIdToDisplay(page.pageId);
1168
- return /* @__PURE__ */ jsxs7("div", { className: "border-b border-neutral-50", children: [
1169
- /* @__PURE__ */ jsxs7("div", { className: "px-4 py-2.5 flex items-center justify-between bg-neutral-50/50", children: [
1170
- /* @__PURE__ */ jsx8("span", { className: "text-xs font-semibold text-neutral-700 truncate", children: displayName }),
1171
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5", children: [
1172
- open.length > 0 && /* @__PURE__ */ jsx8("span", { className: "text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full font-medium", children: open.length }),
1173
- resolved.length > 0 && /* @__PURE__ */ jsx8("span", { className: "text-[10px] bg-neutral-100 text-neutral-500 px-1.5 py-px rounded-full font-medium", children: resolved.length })
1226
+ return /* @__PURE__ */ jsxs8("div", { className: "border-b border-neutral-50", children: [
1227
+ /* @__PURE__ */ jsxs8("div", { className: "px-4 py-2.5 flex items-center justify-between bg-neutral-50/50", children: [
1228
+ /* @__PURE__ */ jsx9("span", { className: "text-xs font-semibold text-neutral-700 truncate", children: displayName }),
1229
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1.5", children: [
1230
+ open.length > 0 && /* @__PURE__ */ jsx9("span", { className: "text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full font-medium", children: open.length }),
1231
+ resolved.length > 0 && /* @__PURE__ */ jsx9("span", { className: "text-[10px] bg-neutral-100 text-neutral-500 px-1.5 py-px rounded-full font-medium", children: resolved.length })
1174
1232
  ] })
1175
1233
  ] }),
1176
1234
  [...open, ...resolved].map((thread) => {
1177
1235
  const firstComment = thread.comments[0];
1178
1236
  if (!firstComment) return null;
1179
1237
  const isResolved = thread.resolved;
1180
- return /* @__PURE__ */ jsxs7(
1238
+ return /* @__PURE__ */ jsxs8(
1181
1239
  "div",
1182
1240
  {
1183
1241
  onClick: () => {
@@ -1186,9 +1244,9 @@ function AllPagesView({
1186
1244
  },
1187
1245
  className: `px-4 py-2.5 border-b border-neutral-50 cursor-pointer hover:bg-neutral-50 transition-colors ${isResolved ? "opacity-50" : ""}`,
1188
1246
  children: [
1189
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between mb-1", children: [
1190
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
1191
- /* @__PURE__ */ jsx8(
1247
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between mb-1", children: [
1248
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1249
+ /* @__PURE__ */ jsx9(
1192
1250
  "div",
1193
1251
  {
1194
1252
  className: "w-4 h-4 rounded-full flex items-center justify-center text-white text-[8px] font-semibold",
@@ -1196,12 +1254,12 @@ function AllPagesView({
1196
1254
  children: firstComment.author.name[0]?.toUpperCase()
1197
1255
  }
1198
1256
  ),
1199
- /* @__PURE__ */ jsx8("span", { className: "text-xs font-medium text-neutral-700", children: firstComment.author.name })
1257
+ /* @__PURE__ */ jsx9("span", { className: "text-xs font-medium text-neutral-700", children: firstComment.author.name })
1200
1258
  ] }),
1201
- /* @__PURE__ */ jsx8("span", { className: "text-[10px] text-neutral-400", children: timeAgo2(firstComment.createdAt) })
1259
+ /* @__PURE__ */ jsx9("span", { className: "text-[10px] text-neutral-400", children: timeAgo2(firstComment.createdAt) })
1202
1260
  ] }),
1203
- /* @__PURE__ */ jsx8("p", { className: "text-xs text-neutral-600 line-clamp-2 pl-6", children: firstComment.body }),
1204
- thread.comments.length > 1 && /* @__PURE__ */ jsxs7("span", { className: "text-[10px] text-neutral-400 pl-6", children: [
1261
+ /* @__PURE__ */ jsx9("p", { className: "text-xs text-neutral-600 line-clamp-2 pl-6", children: firstComment.body }),
1262
+ thread.comments.length > 1 && /* @__PURE__ */ jsxs8("span", { className: "text-[10px] text-neutral-400 pl-6", children: [
1205
1263
  thread.comments.length - 1,
1206
1264
  " ",
1207
1265
  thread.comments.length - 1 === 1 ? "reply" : "replies"
@@ -1223,16 +1281,16 @@ function ThreadItem({
1223
1281
  }) {
1224
1282
  const firstComment = thread.comments[0];
1225
1283
  if (!firstComment) return null;
1226
- return /* @__PURE__ */ jsxs7(
1284
+ return /* @__PURE__ */ jsxs8(
1227
1285
  "div",
1228
1286
  {
1229
1287
  onClick: onSelect,
1230
1288
  className: `px-4 py-3 border-b border-neutral-50 cursor-pointer hover:bg-neutral-50 transition-colors
1231
1289
  ${resolved ? "opacity-60" : ""}`,
1232
1290
  children: [
1233
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between mb-1", children: [
1234
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
1235
- /* @__PURE__ */ jsx8(
1291
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between mb-1", children: [
1292
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1293
+ /* @__PURE__ */ jsx9(
1236
1294
  "div",
1237
1295
  {
1238
1296
  className: "w-4 h-4 rounded-full flex items-center justify-center text-white text-[8px] font-semibold",
@@ -1240,11 +1298,11 @@ function ThreadItem({
1240
1298
  children: firstComment.author.name[0]?.toUpperCase()
1241
1299
  }
1242
1300
  ),
1243
- /* @__PURE__ */ jsx8("span", { className: "text-xs font-medium text-neutral-700", children: firstComment.author.name })
1301
+ /* @__PURE__ */ jsx9("span", { className: "text-xs font-medium text-neutral-700", children: firstComment.author.name })
1244
1302
  ] }),
1245
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
1246
- /* @__PURE__ */ jsx8("span", { className: "text-[10px] text-neutral-400", children: timeAgo2(firstComment.createdAt) }),
1247
- /* @__PURE__ */ jsx8(
1303
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1", children: [
1304
+ /* @__PURE__ */ jsx9("span", { className: "text-[10px] text-neutral-400", children: timeAgo2(firstComment.createdAt) }),
1305
+ /* @__PURE__ */ jsx9(
1248
1306
  "button",
1249
1307
  {
1250
1308
  onClick: (e) => {
@@ -1252,14 +1310,14 @@ function ThreadItem({
1252
1310
  onResolve();
1253
1311
  },
1254
1312
  className: "p-0.5 rounded hover:bg-neutral-200 transition-colors",
1255
- children: resolved ? /* @__PURE__ */ jsx8(Undo22, { className: "w-3 h-3 text-neutral-400" }) : /* @__PURE__ */ jsx8(Check2, { className: "w-3 h-3 text-emerald-600" })
1313
+ children: resolved ? /* @__PURE__ */ jsx9(Undo2, { className: "w-3 h-3 text-neutral-400" }) : /* @__PURE__ */ jsx9(Check, { className: "w-3 h-3 text-emerald-600" })
1256
1314
  }
1257
1315
  )
1258
1316
  ] })
1259
1317
  ] }),
1260
- thread.targetLabel && /* @__PURE__ */ jsx8("span", { className: "inline-block text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium ml-6 mb-1", children: thread.targetLabel }),
1261
- /* @__PURE__ */ jsx8("p", { className: "text-xs text-neutral-600 line-clamp-2 pl-6", children: firstComment.body }),
1262
- thread.comments.length > 1 && /* @__PURE__ */ jsxs7("span", { className: "text-[10px] text-neutral-400 pl-6", children: [
1318
+ thread.targetLabel && /* @__PURE__ */ jsx9("span", { className: "inline-block text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium ml-6 mb-1", children: thread.targetLabel }),
1319
+ /* @__PURE__ */ jsx9("p", { className: "text-xs text-neutral-600 line-clamp-2 pl-6", children: firstComment.body }),
1320
+ thread.comments.length > 1 && /* @__PURE__ */ jsxs8("span", { className: "text-[10px] text-neutral-400 pl-6", children: [
1263
1321
  thread.comments.length - 1,
1264
1322
  " ",
1265
1323
  thread.comments.length - 1 === 1 ? "reply" : "replies"
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.tsx","../src/utils.ts","../src/debug.ts","../src/hooks/use-comments.ts","../src/hooks/use-comment-mode.ts","../src/components/comment-overlay.tsx","../src/components/comment-pin.tsx","../src/components/comment-thread.tsx","../src/components/comment-composer.tsx","../src/components/user-prompt.tsx","../src/components/comment-toggle.tsx","../src/components/comment-sidebar.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useState,\n useEffect,\n useCallback,\n useRef,\n type ReactNode,\n} from \"react\";\nimport type { ApostilThread, ApostilUser, ApostilStorage } from \"./types\";\nimport { createRestAdapter } from \"./adapters/rest\";\nimport { generateId, loadUser, saveUser, getRandomColor } from \"./utils\";\nimport { debug } from \"./debug\";\n\n// Stable default adapter (created once, not per render)\nconst defaultAdapter = createRestAdapter(\"/api/apostil\");\n\ntype ApostilContextValue = {\n threads: ApostilThread[];\n user: ApostilUser | null;\n commentMode: boolean;\n activeThreadId: string | null;\n sidebarOpen: boolean;\n setCommentMode: (on: boolean) => void;\n setActiveThreadId: (id: string | null) => void;\n setSidebarOpen: (open: boolean) => void;\n addThread: (pinX: number, pinY: number, body: string, targetId?: string, targetLabel?: string) => void;\n addReply: (threadId: string, body: string) => void;\n resolveThread: (threadId: string) => void;\n deleteThread: (threadId: string) => void;\n setUser: (name: string) => void;\n unresolvedCount: number;\n};\n\nconst ApostilContext = createContext<ApostilContextValue | null>(null);\n\nexport function ApostilProvider({\n pageId,\n storage,\n children,\n}: {\n pageId: string;\n storage?: ApostilStorage;\n children: ReactNode;\n}) {\n const adapter = storage ?? defaultAdapter;\n const [threads, setThreads] = useState<ApostilThread[]>([]);\n const [user, setUserState] = useState<ApostilUser | null>(null);\n const [commentMode, setCommentMode] = useState(false);\n const [activeThreadId, setActiveThreadId] = useState<string | null>(null);\n const [sidebarOpen, setSidebarOpen] = useState(false);\n const [loaded, setLoaded] = useState(false);\n\n useEffect(() => {\n const saved = loadUser();\n if (saved) setUserState(saved);\n }, []);\n\n // Track current pageId to avoid saving stale data during transitions\n const pageIdRef = useRef(pageId);\n\n useEffect(() => {\n pageIdRef.current = pageId;\n setLoaded(false);\n debug.log(\"loading threads for pageId:\", pageId);\n adapter.load(pageId).then((t) => {\n // Only apply if pageId hasn't changed during the fetch\n if (pageIdRef.current === pageId) {\n debug.log(\"loaded\", t.length, \"threads for\", pageId);\n setThreads(t);\n setLoaded(true);\n }\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pageId]);\n\n useEffect(() => {\n if (loaded && pageIdRef.current === pageId && threads.length > 0) {\n debug.log(\"saving\", threads.length, \"threads for pageId:\", pageId);\n adapter.save(pageId, threads);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [threads, pageId, loaded]);\n\n const setUser = useCallback((name: string) => {\n const u: ApostilUser = { id: generateId(), name, color: getRandomColor() };\n setUserState(u);\n saveUser(u);\n }, []);\n\n const addThread = useCallback(\n (pinX: number, pinY: number, body: string, targetId?: string, targetLabel?: string) => {\n if (!user) return;\n const threadId = generateId();\n const thread: ApostilThread = {\n id: threadId,\n pageId,\n pinX, pinY,\n targetId, targetLabel,\n resolved: false,\n createdAt: new Date().toISOString(),\n comments: [{\n id: generateId(), threadId, author: user, body,\n createdAt: new Date().toISOString(),\n }],\n };\n debug.log(\"new thread:\", { threadId, pinX, pinY, targetId, targetLabel, body });\n setThreads((prev) => [...prev, thread]);\n setActiveThreadId(threadId);\n setCommentMode(false);\n },\n [user, pageId]\n );\n\n const addReply = useCallback(\n (threadId: string, body: string) => {\n if (!user) return;\n setThreads((prev) =>\n prev.map((t) =>\n t.id === threadId\n ? { ...t, comments: [...t.comments, { id: generateId(), threadId, author: user, body, createdAt: new Date().toISOString() }] }\n : t\n )\n );\n },\n [user]\n );\n\n const resolveThread = useCallback((threadId: string) => {\n setThreads((prev) => prev.map((t) => t.id === threadId ? { ...t, resolved: !t.resolved } : t));\n setActiveThreadId(null);\n }, []);\n\n const deleteThread = useCallback((threadId: string) => {\n setThreads((prev) => prev.filter((t) => t.id !== threadId));\n setActiveThreadId(null);\n }, []);\n\n const unresolvedCount = threads.filter((t) => !t.resolved).length;\n\n return (\n <ApostilContext.Provider value={{\n threads, user, commentMode, activeThreadId, sidebarOpen,\n setCommentMode, setActiveThreadId, setSidebarOpen,\n addThread, addReply, resolveThread, deleteThread, setUser,\n unresolvedCount,\n }}>\n {children}\n </ApostilContext.Provider>\n );\n}\n\nexport function useApostil() {\n const ctx = useContext(ApostilContext);\n if (!ctx) throw new Error(\"useApostil must be used within ApostilProvider\");\n return ctx;\n}\n","let counter = 0;\n\nexport function generateId(): string {\n return `${Date.now()}-${++counter}-${Math.random().toString(36).slice(2, 7)}`;\n}\n\nconst USER_KEY = \"apostil-user\";\n\nexport function loadUser(): { id: string; name: string; color: string } | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(USER_KEY);\n return raw ? JSON.parse(raw) : null;\n } catch {\n return null;\n }\n}\n\nexport function saveUser(user: { id: string; name: string; color: string }) {\n if (typeof window === \"undefined\") return;\n localStorage.setItem(USER_KEY, JSON.stringify(user));\n}\n\nconst USER_COLORS = [\n \"#df461c\", \"#2563eb\", \"#16a34a\", \"#9333ea\", \"#ea580c\",\n \"#0891b2\", \"#c026d3\", \"#4f46e5\", \"#059669\", \"#dc2626\",\n];\n\nexport function getRandomColor(): string {\n return USER_COLORS[Math.floor(Math.random() * USER_COLORS.length)];\n}\n","const PREFIX = \"[apostil]\";\n\nfunction isDebug(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return localStorage.getItem(\"apostil-debug\") === \"true\";\n } catch {\n return false;\n }\n}\n\nexport const debug = {\n log(...args: unknown[]) {\n if (isDebug()) console.log(PREFIX, ...args);\n },\n warn(...args: unknown[]) {\n if (isDebug()) console.warn(PREFIX, ...args);\n },\n error(...args: unknown[]) {\n console.error(PREFIX, ...args);\n },\n /** Enable/disable debug logging */\n enable() {\n if (typeof window !== \"undefined\") {\n localStorage.setItem(\"apostil-debug\", \"true\");\n console.log(PREFIX, \"debug logging enabled — reload to take effect\");\n }\n },\n disable() {\n if (typeof window !== \"undefined\") {\n localStorage.removeItem(\"apostil-debug\");\n console.log(PREFIX, \"debug logging disabled\");\n }\n },\n};\n\n// Expose globally for easy toggling from console\nif (typeof window !== \"undefined\") {\n (window as unknown as Record<string, unknown>).__apostil_debug = debug;\n}\n","\"use client\";\n\nimport { useApostil } from \"../context\";\n\nexport function useComments() {\n const { threads, addThread, addReply, resolveThread, deleteThread, unresolvedCount } = useApostil();\n\n return {\n threads,\n openThreads: threads.filter((t) => !t.resolved),\n resolvedThreads: threads.filter((t) => t.resolved),\n addThread,\n addReply,\n resolveThread,\n deleteThread,\n unresolvedCount,\n };\n}\n","\"use client\";\n\nimport { useApostil } from \"../context\";\n\nexport function useCommentMode() {\n const { commentMode, setCommentMode, sidebarOpen, setSidebarOpen } = useApostil();\n\n return {\n commentMode,\n setCommentMode,\n toggleCommentMode: () => setCommentMode(!commentMode),\n sidebarOpen,\n setSidebarOpen,\n toggleSidebar: () => setSidebarOpen(!sidebarOpen),\n };\n}\n","\"use client\";\n\nimport { useState, useCallback, useRef, useEffect } from \"react\";\nimport { useApostil } from \"../context\";\nimport { debug } from \"../debug\";\nimport { CommentPin } from \"./comment-pin\";\nimport { ApostilThreadPopover } from \"./comment-thread\";\nimport { CommentComposer } from \"./comment-composer\";\nimport { UserPrompt } from \"./user-prompt\";\n\ntype PendingPin = {\n x: number;\n y: number;\n targetId?: string;\n targetLabel?: string;\n};\n\n// Find the highest z-index on the page (cached per toggle)\nlet cachedHighZ = 0;\nlet cacheTimestamp = 0;\nfunction getHighestZIndex(): number {\n const now = Date.now();\n if (now - cacheTimestamp < 500) return cachedHighZ; // cache for 500ms\n let max = 0;\n const els = document.querySelectorAll(\"[style*='z-index'], [class*='z-']\");\n for (let i = 0; i < els.length; i++) {\n const z = parseInt(getComputedStyle(els[i]).zIndex, 10);\n if (!isNaN(z) && z > max) max = z;\n }\n cachedHighZ = Math.max(max, 100); // minimum 100\n cacheTimestamp = now;\n return cachedHighZ;\n}\n\n// Semantic elements that are meaningful containers\nconst SEMANTIC_TAGS = new Set([\n \"SECTION\", \"NAV\", \"ASIDE\", \"HEADER\", \"FOOTER\", \"MAIN\",\n \"ARTICLE\", \"FORM\", \"DIALOG\", \"DETAILS\",\n]);\n\n// Min size for a target, and max size ratio to viewport to avoid matching the whole page\nconst MIN_TARGET_SIZE = 50;\nconst MAX_VIEWPORT_RATIO = 0.85; // skip elements covering >85% of viewport in both dimensions\n\n/**\n * Check if a div is a \"visual panel\" — a scrollable area, flex/grid child with defined bounds,\n * or a container with border/background that forms a distinct visual region.\n */\nfunction isVisualPanel(el: HTMLElement): boolean {\n const style = getComputedStyle(el);\n\n // Scrollable container\n if (/auto|scroll/.test(style.overflow + style.overflowY + style.overflowX)) return true;\n\n // Has border or distinct background (likely a card/panel)\n if (style.borderWidth && style.borderWidth !== \"0px\" && style.borderStyle !== \"none\") return true;\n if (style.borderRadius && style.borderRadius !== \"0px\") return true;\n if (style.backgroundColor && style.backgroundColor !== \"rgba(0, 0, 0, 0)\" && style.backgroundColor !== \"transparent\") return true;\n if (style.boxShadow && style.boxShadow !== \"none\") return true;\n\n return false;\n}\n\n/**\n * Try to get a readable label from an element's content.\n * Looks for headings, strong text, or first significant text node nearby.\n */\nfunction inferLabel(el: HTMLElement): string | null {\n // Check for a heading child\n const heading = el.querySelector(\"h1, h2, h3, h4, h5, h6, [class*='title'], [class*='heading']\");\n if (heading) {\n const text = heading.textContent?.trim();\n if (text && text.length <= 40) return text;\n }\n\n // Check for a label-like first child text\n const firstText = el.querySelector(\"span, p, label, strong\");\n if (firstText) {\n const text = firstText.textContent?.trim();\n if (text && text.length <= 30) return text;\n }\n\n return null;\n}\n\n/**\n * Build a stable CSS selector path for an element.\n */\nfunction getElementId(el: HTMLElement): string {\n const manual = el.getAttribute(\"data-comment-target\");\n if (manual) return manual;\n\n if (el.id) return `#${el.id}`;\n\n const label = el.getAttribute(\"aria-label\");\n if (label) return `${el.tagName.toLowerCase()}[aria-label=\"${label}\"]`;\n\n // nth-child path from nearest identifiable ancestor\n const parts: string[] = [];\n let cur: HTMLElement | null = el;\n for (let depth = 0; cur && depth < 5; depth++) {\n if (cur.id) {\n parts.unshift(`#${cur.id}`);\n break;\n }\n const p: HTMLElement | null = cur.parentElement;\n if (p) {\n const siblings = Array.from(p.children);\n const idx = siblings.indexOf(cur);\n parts.unshift(`${cur.tagName.toLowerCase()}:nth-child(${idx + 1})`);\n } else {\n parts.unshift(cur.tagName.toLowerCase());\n }\n cur = p;\n }\n return parts.join(\" > \");\n}\n\n/**\n * Derive a human-readable label for an element.\n */\nfunction getElementLabel(el: HTMLElement): string {\n const manual = el.getAttribute(\"data-comment-label\");\n if (manual) return manual;\n\n const ariaLabel = el.getAttribute(\"aria-label\");\n if (ariaLabel) return ariaLabel;\n\n // Try to infer from content\n const inferred = inferLabel(el);\n if (inferred) return inferred;\n\n const role = el.getAttribute(\"role\");\n\n if (el.id) {\n const pretty = el.id\n .replace(/[-_]/g, \" \")\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n return role ? `${pretty} (${role})` : pretty;\n }\n\n const tag = el.tagName.toLowerCase();\n if (SEMANTIC_TAGS.has(el.tagName)) return role ? `${tag} (${role})` : tag;\n if (role) return role;\n\n return tag;\n}\n\n/**\n * Walk up from the clicked element and find the best container to anchor the comment to.\n * Scores candidates by specificity — prefers the innermost meaningful panel.\n */\nfunction findCommentTarget(el: HTMLElement, boundary: HTMLElement | null) {\n let current: HTMLElement | null = el;\n const candidates: { el: HTMLElement; score: number; depth: number }[] = [];\n let depth = 0;\n\n while (current && current !== boundary && current !== document.body) {\n const rect = current.getBoundingClientRect();\n const isBigEnough = rect.width >= MIN_TARGET_SIZE && rect.height >= MIN_TARGET_SIZE;\n const isTooBig =\n rect.width > window.innerWidth * MAX_VIEWPORT_RATIO &&\n rect.height > window.innerHeight * MAX_VIEWPORT_RATIO;\n\n if (isBigEnough && !isTooBig) {\n let score = 0;\n\n // Manual target — highest priority\n if (current.getAttribute(\"data-comment-target\")) score = 100;\n // id\n else if (current.id) score = 80;\n // aria-label\n else if (current.getAttribute(\"aria-label\")) score = 70;\n // role attribute\n else if (current.getAttribute(\"role\")) score = 60;\n // semantic HTML tag\n else if (SEMANTIC_TAGS.has(current.tagName)) score = 50;\n // Visual panel (scrollable, bordered, shadowed, rounded)\n else if (isVisualPanel(current)) score = 40;\n\n if (score > 0) {\n candidates.push({ el: current, score, depth });\n }\n }\n\n current = current.parentElement;\n depth++;\n }\n\n if (candidates.length === 0) return null;\n\n // Prefer innermost among equally-scored candidates.\n // Among different scores: higher score wins, but add a bonus for being closer to the click.\n candidates.sort((a, b) => {\n const scoreA = a.score + Math.max(0, 10 - a.depth);\n const scoreB = b.score + Math.max(0, 10 - b.depth);\n return scoreB - scoreA;\n });\n\n const best = candidates[0];\n const targetId = getElementId(best.el);\n const targetLabel = getElementLabel(best.el);\n\n debug.log(\" candidates:\", candidates.map((c) => ({\n tag: c.el.tagName.toLowerCase(),\n id: c.el.id || undefined,\n class: c.el.className?.toString().slice(0, 60) || undefined,\n score: c.score,\n depth: c.depth,\n label: getElementLabel(c.el),\n })));\n\n return {\n targetId,\n targetLabel,\n element: best.el,\n };\n}\n\nexport function CommentOverlay() {\n const { threads, commentMode, setCommentMode, user, addThread, setActiveThreadId } =\n useApostil();\n const overlayRef = useRef<HTMLDivElement>(null);\n const [pendingPin, setPendingPin] = useState<PendingPin | null>(null);\n const [pendingPixel, setPendingPixel] = useState<{ left: number; top: number } | null>(null);\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!commentMode || !overlayRef.current) return;\n\n const overlayRect = overlayRef.current.getBoundingClientRect();\n\n // Temporarily hide overlay so elementFromPoint hits the actual content\n const overlay = overlayRef.current;\n overlay.style.pointerEvents = \"none\";\n const elementBelow = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;\n overlay.style.pointerEvents = \"\";\n\n debug.log(\" click at\", { clientX: e.clientX, clientY: e.clientY });\n debug.log(\" element below overlay:\", elementBelow);\n\n // Log the DOM path for debugging\n if (elementBelow) {\n const path: string[] = [];\n let walk: HTMLElement | null = elementBelow;\n while (walk && walk !== document.body) {\n const attrs: string[] = [walk.tagName.toLowerCase()];\n if (walk.id) attrs.push(`#${walk.id}`);\n if (walk.getAttribute(\"data-comment-target\")) attrs.push(`[data-comment-target=\"${walk.getAttribute(\"data-comment-target\")}\"]`);\n if (walk.getAttribute(\"aria-label\")) attrs.push(`[aria-label=\"${walk.getAttribute(\"aria-label\")}\"]`);\n if (walk.getAttribute(\"role\")) attrs.push(`[role=\"${walk.getAttribute(\"role\")}\"]`);\n if (SEMANTIC_TAGS.has(walk.tagName)) attrs.push(\"(semantic)\");\n path.push(attrs.join(\"\"));\n walk = walk.parentElement;\n }\n debug.log(\" DOM path:\", path.join(\" → \"));\n }\n\n const target = elementBelow ? findCommentTarget(elementBelow, overlayRef.current) : null;\n\n if (target) {\n const targetRect = target.element.getBoundingClientRect();\n const x = ((e.clientX - targetRect.left) / targetRect.width) * 100;\n const y = ((e.clientY - targetRect.top) / targetRect.height) * 100;\n debug.log(\" ✅ target found:\", {\n targetId: target.targetId,\n targetLabel: target.targetLabel,\n element: target.element,\n relativePos: { x: x.toFixed(1), y: y.toFixed(1) },\n targetRect: { w: targetRect.width, h: targetRect.height },\n });\n setPendingPin({ x, y, targetId: target.targetId, targetLabel: target.targetLabel });\n } else {\n const x = ((e.clientX - overlayRect.left) / overlayRect.width) * 100;\n const y = ((e.clientY - overlayRect.top) / overlayRect.height) * 100;\n debug.log(\" ⚠️ no target found, using overlay-relative position:\", {\n x: x.toFixed(1),\n y: y.toFixed(1),\n });\n setPendingPin({ x, y });\n }\n\n setPendingPixel({\n left: e.clientX - overlayRect.left,\n top: e.clientY - overlayRect.top,\n });\n setActiveThreadId(null);\n },\n [commentMode, setActiveThreadId]\n );\n\n const handleNewComment = useCallback(\n (body: string) => {\n if (!pendingPin) return;\n debug.log(\" saving thread:\", {\n pinX: pendingPin.x.toFixed(1),\n pinY: pendingPin.y.toFixed(1),\n targetId: pendingPin.targetId ?? \"(none)\",\n targetLabel: pendingPin.targetLabel ?? \"(none)\",\n body,\n });\n addThread(pendingPin.x, pendingPin.y, body, pendingPin.targetId, pendingPin.targetLabel);\n setPendingPin(null);\n setPendingPixel(null);\n },\n [pendingPin, addThread]\n );\n\n const cancelPending = useCallback(() => {\n setPendingPin(null);\n setPendingPixel(null);\n setCommentMode(false);\n }, [setCommentMode]);\n\n // Keyboard shortcuts\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n const tag = (e.target as HTMLElement)?.tagName;\n\n // Escape always works — even when typing\n if (e.key === \"Escape\") {\n if (pendingPin) {\n setPendingPin(null);\n setPendingPixel(null);\n setCommentMode(false);\n } else if (commentMode) {\n setCommentMode(false);\n }\n return;\n }\n\n // Other shortcuts only when not typing\n if (tag === \"INPUT\" || tag === \"TEXTAREA\") return;\n\n if (e.key === \"c\" || e.key === \"C\") {\n if (!commentMode) {\n debug.log(\" comment mode ON\");\n setActiveThreadId(null);\n setCommentMode(true);\n } else {\n debug.log(\" comment mode OFF\");\n setCommentMode(false);\n }\n }\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [commentMode, pendingPin, setCommentMode, setActiveThreadId]);\n\n // Sort: unresolved first, then resolved\n const sortedThreads = [...threads].sort((a, b) => {\n if (a.resolved !== b.resolved) return a.resolved ? 1 : -1;\n return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();\n });\n\n return (\n <>\n <UserPrompt />\n\n {/* Overlay layer */}\n <div\n ref={overlayRef}\n className={`fixed inset-0 ${\n commentMode\n ? \"cursor-crosshair pointer-events-auto\"\n : \"pointer-events-none\"\n }`}\n style={{ zIndex: commentMode ? getHighestZIndex() + 10 : 55 }}\n onMouseDown={handleClick}\n >\n {/* Existing pins */}\n {sortedThreads.map((thread, i) => (\n <div key={thread.id} className=\"pointer-events-auto\">\n <CommentPin thread={thread} index={i} overlayRef={overlayRef} />\n <ApostilThreadPopover thread={thread} overlayRef={overlayRef} />\n </div>\n ))}\n\n {/* Pending pin */}\n {pendingPin && pendingPixel && user && (\n <div\n className=\"absolute z-[70] pointer-events-auto\"\n style={{\n left: pendingPixel.left,\n top: pendingPixel.top,\n }}\n onMouseDown={(e) => e.stopPropagation()}\n onClick={(e) => e.stopPropagation()}\n >\n <div\n className=\"absolute -translate-x-1/2 -translate-y-1/2 w-7 h-7 rounded-full\n flex items-center justify-center text-white text-xs font-semibold\n shadow-lg ring-2 ring-white ring-offset-2 animate-bounce\"\n style={{ backgroundColor: user.color }}\n >\n +\n </div>\n <div className=\"absolute ml-5 -mt-3 w-72\">\n <div className=\"bg-white rounded-xl shadow-2xl border border-neutral-200 p-3\">\n <div className=\"flex items-center gap-2 mb-2\">\n <p className=\"text-xs text-neutral-500\">New comment</p>\n {pendingPin.targetLabel && (\n <span className=\"text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium\">\n {pendingPin.targetLabel}\n </span>\n )}\n </div>\n <CommentComposer\n onSubmit={handleNewComment}\n placeholder=\"What's on your mind?\"\n autoFocus\n />\n <button\n onClick={cancelPending}\n className=\"mt-2 text-xs text-neutral-400 hover:text-neutral-600 transition-colors\"\n >\n Cancel\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Comment mode hint */}\n {commentMode && !pendingPin && (\n <div className=\"absolute bottom-6 left-1/2 -translate-x-1/2 z-[60] pointer-events-none\">\n <div className=\"bg-neutral-900/80 text-white text-sm px-4 py-2 rounded-full backdrop-blur-sm\">\n Click anywhere to add a comment\n </div>\n </div>\n )}\n </>\n );\n}\n","\"use client\";\n\nimport { useState, useEffect, useCallback, type RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useApostil } from \"../context\";\nimport type { ApostilThread } from \"../types\";\n\n/**\n * Find the target element using the stored targetId.\n * targetId can be a CSS selector or a data-comment-target value.\n */\nfunction findTargetElement(targetId: string): HTMLElement | null {\n // First try as a CSS selector\n try {\n const el = document.querySelector(targetId);\n if (el instanceof HTMLElement) return el;\n } catch {\n // Invalid selector — fall through\n }\n\n // Then try as a data-comment-target value\n try {\n const el = document.querySelector(`[data-comment-target=\"${CSS.escape(targetId)}\"]`);\n if (el instanceof HTMLElement) return el;\n } catch {\n // noop\n }\n\n return null;\n}\n\n/**\n * Resolves pixel position for a thread pin relative to the overlay.\n * Used only for NON-targeted pins (no targetId).\n */\nfunction resolveOverlayPosition(\n thread: ApostilThread,\n overlayEl: HTMLElement | null\n): { left: number; top: number } | null {\n if (!overlayEl) return null;\n const overlayRect = overlayEl.getBoundingClientRect();\n return {\n left: (thread.pinX / 100) * overlayRect.width,\n top: (thread.pinY / 100) * overlayRect.height,\n };\n}\n\n/**\n * Resolves pixel position relative to the overlay, for targeted pins.\n * Used by the thread popover which always renders in the overlay.\n */\nfunction resolvePosition(\n thread: ApostilThread,\n overlayEl: HTMLElement | null\n): { left: number; top: number } | null {\n if (!overlayEl) return null;\n const overlayRect = overlayEl.getBoundingClientRect();\n\n if (thread.targetId) {\n const target = findTargetElement(thread.targetId);\n if (target) {\n const targetRect = target.getBoundingClientRect();\n return {\n left: targetRect.left - overlayRect.left + (thread.pinX / 100) * targetRect.width,\n top: targetRect.top - overlayRect.top + (thread.pinY / 100) * targetRect.height,\n };\n }\n return null;\n }\n\n return {\n left: (thread.pinX / 100) * overlayRect.width,\n top: (thread.pinY / 100) * overlayRect.height,\n };\n}\n\n// ─── Pin button (shared rendering) ────────────────────────────────\n\nfunction PinButton({\n thread,\n index,\n isActive,\n onClick,\n}: {\n thread: ApostilThread;\n index: number;\n isActive: boolean;\n onClick: (e: React.MouseEvent) => void;\n}) {\n const authorColor = thread.comments[0]?.author.color ?? \"#df461c\";\n\n return (\n <button onClick={onClick} className=\"group\" style={{ position: \"relative\" }}>\n <div\n className={`\n flex items-center justify-center\n w-7 h-7 rounded-full text-white text-xs font-semibold\n shadow-lg cursor-pointer\n transition-all duration-200\n ${isActive ? \"scale-125 ring-2 ring-white ring-offset-2\" : \"hover:scale-110\"}\n ${thread.resolved ? \"opacity-40\" : \"\"}\n `}\n style={{ backgroundColor: authorColor }}\n >\n {index + 1}\n </div>\n {!thread.resolved && !isActive && (\n <div\n className=\"absolute inset-0 rounded-full animate-ping opacity-20\"\n style={{ backgroundColor: authorColor }}\n />\n )}\n {thread.targetLabel && (\n <div className=\"absolute -bottom-6 left-1/2 -translate-x-1/2 whitespace-nowrap\n opacity-0 group-hover:opacity-100 transition-opacity\n text-[10px] bg-neutral-800 text-white px-1.5 py-0.5 rounded pointer-events-none\">\n {thread.targetLabel}\n </div>\n )}\n </button>\n );\n}\n\n// ─── Targeted pin (portals into the target element) ───────────────\n\nfunction TargetedPin({\n thread,\n index,\n}: {\n thread: ApostilThread;\n index: number;\n}) {\n const { activeThreadId, setActiveThreadId } = useApostil();\n const isActive = activeThreadId === thread.id;\n const [targetEl, setTargetEl] = useState<HTMLElement | null>(null);\n\n useEffect(() => {\n if (!thread.targetId) return;\n\n function tryFind() {\n const el = findTargetElement(thread.targetId!);\n if (el) {\n const pos = getComputedStyle(el).position;\n if (pos === \"static\") el.style.position = \"relative\";\n setTargetEl(el);\n } else {\n setTargetEl(null);\n }\n }\n\n tryFind();\n\n // Watch for DOM changes — target element may appear/disappear (popovers, dialogs)\n const observer = new MutationObserver(() => tryFind());\n observer.observe(document.body, { childList: true, subtree: true });\n\n return () => observer.disconnect();\n }, [thread.targetId]);\n\n if (!targetEl) return null;\n\n return createPortal(\n <div\n className=\"absolute pointer-events-auto\"\n style={{\n left: `${thread.pinX}%`,\n top: `${thread.pinY}%`,\n transform: \"translate(-50%, -50%)\",\n zIndex: 10,\n }}\n >\n <PinButton\n thread={thread}\n index={index}\n isActive={isActive}\n onClick={(e) => {\n e.stopPropagation();\n setActiveThreadId(isActive ? null : thread.id);\n }}\n />\n </div>,\n targetEl\n );\n}\n\n// ─── Overlay pin (non-targeted, positioned in the overlay) ────────\n\nfunction OverlayPin({\n thread,\n index,\n overlayRef,\n}: {\n thread: ApostilThread;\n index: number;\n overlayRef: RefObject<HTMLDivElement | null>;\n}) {\n const { activeThreadId, setActiveThreadId } = useApostil();\n const isActive = activeThreadId === thread.id;\n const [pos, setPos] = useState<{ left: number; top: number } | null>(null);\n\n const updatePos = useCallback(() => {\n setPos(resolveOverlayPosition(thread, overlayRef.current));\n }, [thread, overlayRef]);\n\n useEffect(() => {\n updatePos();\n window.addEventListener(\"resize\", updatePos);\n return () => window.removeEventListener(\"resize\", updatePos);\n }, [updatePos]);\n\n if (!pos) return null;\n\n return (\n <div\n className=\"absolute pointer-events-auto\"\n style={{\n left: pos.left,\n top: pos.top,\n transform: \"translate(-50%, -50%)\",\n zIndex: 60,\n }}\n >\n <PinButton\n thread={thread}\n index={index}\n isActive={isActive}\n onClick={(e) => {\n e.stopPropagation();\n setActiveThreadId(isActive ? null : thread.id);\n }}\n />\n </div>\n );\n}\n\n// ─── Public CommentPin (delegates to targeted or overlay) ─────────\n\nexport function CommentPin({\n thread,\n index,\n overlayRef,\n}: {\n thread: ApostilThread;\n index: number;\n overlayRef: RefObject<HTMLDivElement | null>;\n}) {\n if (thread.targetId) {\n return <TargetedPin key={`targeted-${thread.id}`} thread={thread} index={index} />;\n }\n return <OverlayPin key={`overlay-${thread.id}`} thread={thread} index={index} overlayRef={overlayRef} />;\n}\n\nexport { resolvePosition, findTargetElement };\n","\"use client\";\n\nimport { useEffect, useRef, useState, useCallback, type RefObject } from \"react\";\nimport { Check, Trash2, Undo2 } from \"lucide-react\";\nimport { useApostil } from \"../context\";\nimport { CommentComposer } from \"./comment-composer\";\nimport { resolvePosition } from \"./comment-pin\";\nimport type { ApostilThread as ApostilThreadType } from \"../types\";\n\nfunction timeAgo(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return \"just now\";\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.floor(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n\nexport function ApostilThreadPopover({\n thread,\n overlayRef,\n}: {\n thread: ApostilThreadType;\n overlayRef: RefObject<HTMLDivElement | null>;\n}) {\n const { activeThreadId, setActiveThreadId, addReply, resolveThread, deleteThread, user } =\n useApostil();\n const ref = useRef<HTMLDivElement>(null);\n const isOpen = activeThreadId === thread.id;\n\n const [pos, setPos] = useState<{ left: number; top: number } | null>(null);\n\n const updatePos = useCallback(() => {\n setPos(resolvePosition(thread, overlayRef.current));\n }, [thread, overlayRef]);\n\n useEffect(() => {\n if (!isOpen) return;\n updatePos();\n window.addEventListener(\"resize\", updatePos);\n // Track scroll on any container so popover follows the pin\n document.addEventListener(\"scroll\", updatePos, true);\n return () => {\n window.removeEventListener(\"resize\", updatePos);\n document.removeEventListener(\"scroll\", updatePos, true);\n };\n }, [isOpen, updatePos]);\n\n useEffect(() => {\n if (!isOpen) return;\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setActiveThreadId(null);\n }\n };\n const timer = setTimeout(() => document.addEventListener(\"mousedown\", handler), 0);\n return () => {\n clearTimeout(timer);\n document.removeEventListener(\"mousedown\", handler);\n };\n }, [isOpen, setActiveThreadId]);\n\n if (!isOpen || !pos) return null;\n\n return (\n <div\n ref={ref}\n className=\"absolute z-[70] ml-5 -mt-3\"\n style={{ left: pos.left, top: pos.top }}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"w-80 bg-white rounded-xl shadow-2xl border border-neutral-200 overflow-hidden\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-2.5 border-b border-neutral-100 bg-neutral-50\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-medium text-neutral-500\">\n {thread.comments.length} {thread.comments.length === 1 ? \"comment\" : \"comments\"}\n </span>\n {thread.targetLabel && (\n <span className=\"text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium\">\n {thread.targetLabel}\n </span>\n )}\n {thread.resolved && (\n <span className=\"text-[10px] text-emerald-600 font-medium\">Resolved</span>\n )}\n </div>\n <div className=\"flex gap-1\">\n <button\n onClick={() => resolveThread(thread.id)}\n className=\"p-1 rounded hover:bg-neutral-200 transition-colors\"\n title={thread.resolved ? \"Reopen\" : \"Resolve\"}\n >\n {thread.resolved ? (\n <Undo2 className=\"w-3.5 h-3.5 text-neutral-500\" />\n ) : (\n <Check className=\"w-3.5 h-3.5 text-emerald-600\" />\n )}\n </button>\n <button\n onClick={() => deleteThread(thread.id)}\n className=\"p-1 rounded hover:bg-red-50 transition-colors\"\n title=\"Delete thread\"\n >\n <Trash2 className=\"w-3.5 h-3.5 text-neutral-400 hover:text-red-500\" />\n </button>\n </div>\n </div>\n\n {/* Comments */}\n <div className=\"max-h-64 overflow-y-auto\">\n {thread.comments.map((comment) => (\n <div key={comment.id} className=\"px-4 py-3 border-b border-neutral-50 last:border-0\">\n <div className=\"flex items-center gap-2 mb-1\">\n <div\n className=\"w-5 h-5 rounded-full flex items-center justify-center text-white text-[10px] font-semibold shrink-0\"\n style={{ backgroundColor: comment.author.color }}\n >\n {comment.author.name[0]?.toUpperCase()}\n </div>\n <span className=\"text-xs font-medium text-neutral-800\">\n {comment.author.name}\n </span>\n <span className=\"text-[10px] text-neutral-400 ml-auto\">\n {timeAgo(comment.createdAt)}\n </span>\n </div>\n <p className=\"text-sm text-neutral-700 leading-relaxed pl-7\">\n {comment.body}\n </p>\n </div>\n ))}\n </div>\n\n {/* Reply */}\n {user && !thread.resolved && (\n <div className=\"px-3 py-2.5 border-t border-neutral-100 bg-neutral-50/50\">\n <CommentComposer\n onSubmit={(body) => addReply(thread.id, body)}\n placeholder=\"Reply...\"\n autoFocus\n />\n </div>\n )}\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { useState, useRef, useEffect } from \"react\";\nimport { Send } from \"lucide-react\";\n\nexport function CommentComposer({\n onSubmit,\n placeholder = \"Add a comment...\",\n autoFocus = false,\n}: {\n onSubmit: (body: string) => void;\n placeholder?: string;\n autoFocus?: boolean;\n}) {\n const [value, setValue] = useState(\"\");\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n if (autoFocus) {\n inputRef.current?.focus();\n }\n }, [autoFocus]);\n\n const handleSubmit = () => {\n const trimmed = value.trim();\n if (!trimmed) return;\n onSubmit(trimmed);\n setValue(\"\");\n };\n\n return (\n <div className=\"flex gap-2 items-end\">\n <textarea\n ref={inputRef}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n }}\n placeholder={placeholder}\n rows={1}\n className=\"flex-1 resize-none rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm\n placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-neutral-300\n min-h-[36px] max-h-[120px]\"\n />\n <button\n onClick={handleSubmit}\n disabled={!value.trim()}\n className=\"flex items-center justify-center w-8 h-8 rounded-lg\n bg-neutral-900 text-white disabled:opacity-30\n hover:bg-neutral-700 transition-colors shrink-0\"\n >\n <Send className=\"w-3.5 h-3.5\" />\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport { useState } from \"react\";\nimport { useApostil } from \"../context\";\n\nexport function UserPrompt() {\n const { user, setUser, commentMode } = useApostil();\n const [name, setName] = useState(\"\");\n\n // Only show when comment mode is activated and no user is set\n if (user || !commentMode) return null;\n\n const handleSubmit = () => {\n const trimmed = name.trim();\n if (!trimmed) return;\n setUser(trimmed);\n };\n\n return (\n <div className=\"absolute inset-0 z-[80] flex items-center justify-center bg-black/20 backdrop-blur-[2px]\">\n <div className=\"bg-white rounded-xl shadow-2xl border border-neutral-200 p-6 w-80\">\n <h3 className=\"text-sm font-semibold text-neutral-900 mb-1\">\n What&apos;s your name?\n </h3>\n <p className=\"text-xs text-neutral-500 mb-4\">\n This will be shown with your comments.\n </p>\n <input\n value={name}\n onChange={(e) => setName(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") handleSubmit();\n }}\n placeholder=\"Enter your name\"\n autoFocus\n className=\"w-full rounded-lg border border-neutral-200 px-3 py-2 text-sm\n placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-neutral-300 mb-3\"\n />\n <button\n onClick={handleSubmit}\n disabled={!name.trim()}\n className=\"w-full py-2 rounded-lg bg-neutral-900 text-white text-sm font-medium\n disabled:opacity-30 hover:bg-neutral-700 transition-colors\"\n >\n Continue\n </button>\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { MessageSquare, List, X } from \"lucide-react\";\nimport { useApostil } from \"../context\";\n\nexport function CommentToggle() {\n const {\n commentMode,\n setCommentMode,\n sidebarOpen,\n setSidebarOpen,\n unresolvedCount,\n setActiveThreadId,\n } = useApostil();\n\n return (\n <div className=\"absolute bottom-5 right-5 z-[65] flex flex-col gap-2 items-end\">\n {/* Sidebar toggle */}\n <button\n onClick={() => setSidebarOpen(!sidebarOpen)}\n className={`flex items-center justify-center w-10 h-10 rounded-full shadow-lg\n transition-all duration-200 hover:scale-105\n ${sidebarOpen\n ? \"bg-neutral-900 text-white\"\n : \"bg-white text-neutral-600 hover:bg-neutral-50 border border-neutral-200\"\n }`}\n title=\"Toggle comment list\"\n >\n <List className=\"w-4 h-4\" />\n </button>\n\n {/* Comment mode toggle */}\n <button\n onClick={() => {\n if (commentMode) {\n setCommentMode(false);\n } else {\n setActiveThreadId(null);\n setCommentMode(true);\n }\n }}\n className={`relative flex items-center justify-center w-12 h-12 rounded-full shadow-lg\n transition-all duration-200 hover:scale-105\n ${commentMode\n ? \"bg-neutral-900 text-white ring-2 ring-neutral-400\"\n : \"bg-white text-neutral-700 hover:bg-neutral-50 border border-neutral-200\"\n }`}\n title={commentMode ? \"Exit comment mode\" : \"Add comment\"}\n >\n {commentMode ? (\n <X className=\"w-5 h-5\" />\n ) : (\n <MessageSquare className=\"w-5 h-5\" />\n )}\n {/* Unresolved badge */}\n {unresolvedCount > 0 && !commentMode && (\n <span className=\"absolute -top-1 -right-1 min-w-[18px] h-[18px] rounded-full\n bg-red-500 text-white text-[10px] font-semibold\n flex items-center justify-center px-1\">\n {unresolvedCount}\n </span>\n )}\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { X, Check, Undo2, MessageSquare, Globe, FileText } from \"lucide-react\";\nimport { useApostil } from \"../context\";\nimport type { ApostilThread } from \"../types\";\n\nfunction timeAgo(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return \"just now\";\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.floor(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n\nfunction pageIdToDisplay(pageId: string): string {\n return pageId.replace(/--/g, \"/\").replace(/-/g, \".\");\n}\n\ntype AllPagesData = { pageId: string; threads: ApostilThread[] }[];\n\nexport function CommentSidebar() {\n const {\n threads,\n sidebarOpen,\n setSidebarOpen,\n setActiveThreadId,\n resolveThread,\n } = useApostil();\n\n const [tab, setTab] = useState<\"page\" | \"all\">(\"page\");\n const [allPages, setAllPages] = useState<AllPagesData>([]);\n const [loadingAll, setLoadingAll] = useState(false);\n\n // Fetch all pages when \"All Pages\" tab is selected\n useEffect(() => {\n if (!sidebarOpen || tab !== \"all\") return;\n setLoadingAll(true);\n\n // Try CLI server first, then local API\n async function fetchAll() {\n // Fetch from the app's own API route (no pageId = returns all pages)\n try {\n const res = await fetch(\"/api/apostil\");\n if (res.ok) {\n const data = await res.json();\n setAllPages(data);\n }\n } catch {}\n setLoadingAll(false);\n }\n fetchAll();\n }, [sidebarOpen, tab]);\n\n if (!sidebarOpen) return null;\n\n const openThreads = threads.filter((t) => !t.resolved);\n const resolvedThreads = threads.filter((t) => t.resolved);\n\n return (\n <div className=\"absolute top-0 right-0 bottom-0 w-80 z-[75] bg-white border-l border-neutral-200 shadow-xl flex flex-col\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-neutral-100\">\n <div className=\"flex items-center gap-2\">\n <MessageSquare className=\"w-4 h-4 text-neutral-500\" />\n <span className=\"text-sm font-semibold text-neutral-900\">\n Comments\n </span>\n </div>\n <button\n onClick={() => setSidebarOpen(false)}\n className=\"p-1 rounded hover:bg-neutral-100 transition-colors\"\n >\n <X className=\"w-4 h-4 text-neutral-500\" />\n </button>\n </div>\n\n {/* Tabs */}\n <div className=\"flex border-b border-neutral-100\">\n <button\n onClick={() => setTab(\"page\")}\n className={`flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${\n tab === \"page\"\n ? \"text-neutral-900 border-b-2 border-neutral-900\"\n : \"text-neutral-400 hover:text-neutral-600\"\n }`}\n >\n <FileText className=\"w-3 h-3\" />\n This Page\n {openThreads.length > 0 && (\n <span className=\"text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full\">\n {openThreads.length}\n </span>\n )}\n </button>\n <button\n onClick={() => setTab(\"all\")}\n className={`flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${\n tab === \"all\"\n ? \"text-neutral-900 border-b-2 border-neutral-900\"\n : \"text-neutral-400 hover:text-neutral-600\"\n }`}\n >\n <Globe className=\"w-3 h-3\" />\n All Pages\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-y-auto\">\n {tab === \"page\" ? (\n <PageThreads\n threads={threads}\n openThreads={openThreads}\n resolvedThreads={resolvedThreads}\n onSelect={setActiveThreadId}\n onResolve={resolveThread}\n />\n ) : (\n <AllPagesView\n pages={allPages}\n loading={loadingAll}\n />\n )}\n </div>\n </div>\n );\n}\n\n// --- This Page tab ---\n\nfunction PageThreads({\n threads,\n openThreads,\n resolvedThreads,\n onSelect,\n onResolve,\n}: {\n threads: ApostilThread[];\n openThreads: ApostilThread[];\n resolvedThreads: ApostilThread[];\n onSelect: (id: string) => void;\n onResolve: (id: string) => void;\n}) {\n if (threads.length === 0) {\n return (\n <div className=\"p-6 text-center text-sm text-neutral-400\">\n No comments on this page.\n </div>\n );\n }\n\n return (\n <>\n {openThreads.length > 0 && (\n <div>\n <div className=\"px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider\">\n Open ({openThreads.length})\n </div>\n {openThreads.map((thread) => (\n <ThreadItem\n key={thread.id}\n thread={thread}\n onSelect={() => onSelect(thread.id)}\n onResolve={() => onResolve(thread.id)}\n />\n ))}\n </div>\n )}\n\n {resolvedThreads.length > 0 && (\n <div>\n <div className=\"px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider\">\n Resolved ({resolvedThreads.length})\n </div>\n {resolvedThreads.map((thread) => (\n <ThreadItem\n key={thread.id}\n thread={thread}\n onSelect={() => onSelect(thread.id)}\n onResolve={() => onResolve(thread.id)}\n resolved\n />\n ))}\n </div>\n )}\n </>\n );\n}\n\n// --- All Pages tab ---\n\nfunction AllPagesView({\n pages,\n loading,\n}: {\n pages: AllPagesData;\n loading: boolean;\n}) {\n if (loading) {\n return (\n <div className=\"p-6 text-center text-sm text-neutral-400\">\n Loading...\n </div>\n );\n }\n\n if (pages.length === 0) {\n return (\n <div className=\"p-6 text-center text-sm text-neutral-400\">\n No comments in this project yet.\n </div>\n );\n }\n\n const totalOpen = pages.reduce((s, p) => s + p.threads.filter((t) => !t.resolved).length, 0);\n\n return (\n <>\n {totalOpen > 0 && (\n <div className=\"px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider\">\n {totalOpen} open across {pages.length} pages\n </div>\n )}\n\n {pages.map((page) => {\n const open = page.threads.filter((t) => !t.resolved);\n const resolved = page.threads.filter((t) => t.resolved);\n const displayName = pageIdToDisplay(page.pageId);\n\n return (\n <div key={page.pageId} className=\"border-b border-neutral-50\">\n {/* Page header */}\n <div className=\"px-4 py-2.5 flex items-center justify-between bg-neutral-50/50\">\n <span className=\"text-xs font-semibold text-neutral-700 truncate\">\n {displayName}\n </span>\n <div className=\"flex items-center gap-1.5\">\n {open.length > 0 && (\n <span className=\"text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full font-medium\">\n {open.length}\n </span>\n )}\n {resolved.length > 0 && (\n <span className=\"text-[10px] bg-neutral-100 text-neutral-500 px-1.5 py-px rounded-full font-medium\">\n {resolved.length}\n </span>\n )}\n </div>\n </div>\n\n {/* Threads for this page */}\n {[...open, ...resolved].map((thread) => {\n const firstComment = thread.comments[0];\n if (!firstComment) return null;\n const isResolved = thread.resolved;\n\n return (\n <div\n key={thread.id}\n onClick={() => {\n // Navigate to the page with the comment hash\n const path = \"/\" + page.pageId.replace(/--/g, \"/\");\n window.location.href = path + \"#apostil-\" + thread.id;\n }}\n className={`px-4 py-2.5 border-b border-neutral-50 cursor-pointer hover:bg-neutral-50 transition-colors ${\n isResolved ? \"opacity-50\" : \"\"\n }`}\n >\n <div className=\"flex items-center justify-between mb-1\">\n <div className=\"flex items-center gap-2\">\n <div\n className=\"w-4 h-4 rounded-full flex items-center justify-center text-white text-[8px] font-semibold\"\n style={{ backgroundColor: firstComment.author.color }}\n >\n {firstComment.author.name[0]?.toUpperCase()}\n </div>\n <span className=\"text-xs font-medium text-neutral-700\">\n {firstComment.author.name}\n </span>\n </div>\n <span className=\"text-[10px] text-neutral-400\">\n {timeAgo(firstComment.createdAt)}\n </span>\n </div>\n <p className=\"text-xs text-neutral-600 line-clamp-2 pl-6\">\n {firstComment.body}\n </p>\n {thread.comments.length > 1 && (\n <span className=\"text-[10px] text-neutral-400 pl-6\">\n {thread.comments.length - 1} {thread.comments.length - 1 === 1 ? \"reply\" : \"replies\"}\n </span>\n )}\n </div>\n );\n })}\n </div>\n );\n })}\n </>\n );\n}\n\n// --- Shared thread item ---\n\nfunction ThreadItem({\n thread,\n onSelect,\n onResolve,\n resolved,\n}: {\n thread: { id: string; targetLabel?: string; comments: { author: { name: string; color: string }; body: string; createdAt: string }[] };\n onSelect: () => void;\n onResolve: () => void;\n resolved?: boolean;\n}) {\n const firstComment = thread.comments[0];\n if (!firstComment) return null;\n\n return (\n <div\n onClick={onSelect}\n className={`px-4 py-3 border-b border-neutral-50 cursor-pointer hover:bg-neutral-50 transition-colors\n ${resolved ? \"opacity-60\" : \"\"}`}\n >\n <div className=\"flex items-center justify-between mb-1\">\n <div className=\"flex items-center gap-2\">\n <div\n className=\"w-4 h-4 rounded-full flex items-center justify-center text-white text-[8px] font-semibold\"\n style={{ backgroundColor: firstComment.author.color }}\n >\n {firstComment.author.name[0]?.toUpperCase()}\n </div>\n <span className=\"text-xs font-medium text-neutral-700\">\n {firstComment.author.name}\n </span>\n </div>\n <div className=\"flex items-center gap-1\">\n <span className=\"text-[10px] text-neutral-400\">\n {timeAgo(firstComment.createdAt)}\n </span>\n <button\n onClick={(e) => {\n e.stopPropagation();\n onResolve();\n }}\n className=\"p-0.5 rounded hover:bg-neutral-200 transition-colors\"\n >\n {resolved ? (\n <Undo2 className=\"w-3 h-3 text-neutral-400\" />\n ) : (\n <Check className=\"w-3 h-3 text-emerald-600\" />\n )}\n </button>\n </div>\n </div>\n {thread.targetLabel && (\n <span className=\"inline-block text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium ml-6 mb-1\">\n {thread.targetLabel}\n </span>\n )}\n <p className=\"text-xs text-neutral-600 line-clamp-2 pl-6\">\n {firstComment.body}\n </p>\n {thread.comments.length > 1 && (\n <span className=\"text-[10px] text-neutral-400 pl-6\">\n {thread.comments.length - 1} {thread.comments.length - 1 === 1 ? \"reply\" : \"replies\"}\n </span>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;;;ACVP,IAAI,UAAU;AAEP,SAAS,aAAqB;AACnC,SAAO,GAAG,KAAK,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC7E;AAEA,IAAM,WAAW;AAEV,SAAS,WAA+D;AAC7E,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,UAAM,MAAM,aAAa,QAAQ,QAAQ;AACzC,WAAO,MAAM,KAAK,MAAM,GAAG,IAAI;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,SAAS,MAAmD;AAC1E,MAAI,OAAO,WAAW,YAAa;AACnC,eAAa,QAAQ,UAAU,KAAK,UAAU,IAAI,CAAC;AACrD;AAEA,IAAM,cAAc;AAAA,EAClB;AAAA,EAAW;AAAA,EAAW;AAAA,EAAW;AAAA,EAAW;AAAA,EAC5C;AAAA,EAAW;AAAA,EAAW;AAAA,EAAW;AAAA,EAAW;AAC9C;AAEO,SAAS,iBAAyB;AACvC,SAAO,YAAY,KAAK,MAAM,KAAK,OAAO,IAAI,YAAY,MAAM,CAAC;AACnE;;;AC9BA,IAAM,SAAS;AAEf,SAAS,UAAmB;AAC1B,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,WAAO,aAAa,QAAQ,eAAe,MAAM;AAAA,EACnD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,IAAM,QAAQ;AAAA,EACnB,OAAO,MAAiB;AACtB,QAAI,QAAQ,EAAG,SAAQ,IAAI,QAAQ,GAAG,IAAI;AAAA,EAC5C;AAAA,EACA,QAAQ,MAAiB;AACvB,QAAI,QAAQ,EAAG,SAAQ,KAAK,QAAQ,GAAG,IAAI;AAAA,EAC7C;AAAA,EACA,SAAS,MAAiB;AACxB,YAAQ,MAAM,QAAQ,GAAG,IAAI;AAAA,EAC/B;AAAA;AAAA,EAEA,SAAS;AACP,QAAI,OAAO,WAAW,aAAa;AACjC,mBAAa,QAAQ,iBAAiB,MAAM;AAC5C,cAAQ,IAAI,QAAQ,oDAA+C;AAAA,IACrE;AAAA,EACF;AAAA,EACA,UAAU;AACR,QAAI,OAAO,WAAW,aAAa;AACjC,mBAAa,WAAW,eAAe;AACvC,cAAQ,IAAI,QAAQ,wBAAwB;AAAA,IAC9C;AAAA,EACF;AACF;AAGA,IAAI,OAAO,WAAW,aAAa;AACjC,EAAC,OAA8C,kBAAkB;AACnE;;;AFwGI;AA9HJ,IAAM,iBAAiB,kBAAkB,cAAc;AAmBvD,IAAM,iBAAiB,cAA0C,IAAI;AAE9D,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,UAAU,WAAW;AAC3B,QAAM,CAAC,SAAS,UAAU,IAAI,SAA0B,CAAC,CAAC;AAC1D,QAAM,CAAC,MAAM,YAAY,IAAI,SAA6B,IAAI;AAC9D,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAwB,IAAI;AACxE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAE1C,YAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,MAAO,cAAa,KAAK;AAAA,EAC/B,GAAG,CAAC,CAAC;AAGL,QAAM,YAAY,OAAO,MAAM;AAE/B,YAAU,MAAM;AACd,cAAU,UAAU;AACpB,cAAU,KAAK;AACf,UAAM,IAAI,+BAA+B,MAAM;AAC/C,YAAQ,KAAK,MAAM,EAAE,KAAK,CAAC,MAAM;AAE/B,UAAI,UAAU,YAAY,QAAQ;AAChC,cAAM,IAAI,UAAU,EAAE,QAAQ,eAAe,MAAM;AACnD,mBAAW,CAAC;AACZ,kBAAU,IAAI;AAAA,MAChB;AAAA,IACF,CAAC;AAAA,EAEH,GAAG,CAAC,MAAM,CAAC;AAEX,YAAU,MAAM;AACd,QAAI,UAAU,UAAU,YAAY,UAAU,QAAQ,SAAS,GAAG;AAChE,YAAM,IAAI,UAAU,QAAQ,QAAQ,uBAAuB,MAAM;AACjE,cAAQ,KAAK,QAAQ,OAAO;AAAA,IAC9B;AAAA,EAEF,GAAG,CAAC,SAAS,QAAQ,MAAM,CAAC;AAE5B,QAAM,UAAU,YAAY,CAAC,SAAiB;AAC5C,UAAM,IAAiB,EAAE,IAAI,WAAW,GAAG,MAAM,OAAO,eAAe,EAAE;AACzE,iBAAa,CAAC;AACd,aAAS,CAAC;AAAA,EACZ,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY;AAAA,IAChB,CAAC,MAAc,MAAc,MAAc,UAAmB,gBAAyB;AACrF,UAAI,CAAC,KAAM;AACX,YAAM,WAAW,WAAW;AAC5B,YAAM,SAAwB;AAAA,QAC5B,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QAAM;AAAA,QACN;AAAA,QAAU;AAAA,QACV,UAAU;AAAA,QACV,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,UAAU,CAAC;AAAA,UACT,IAAI,WAAW;AAAA,UAAG;AAAA,UAAU,QAAQ;AAAA,UAAM;AAAA,UAC1C,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QACpC,CAAC;AAAA,MACH;AACA,YAAM,IAAI,eAAe,EAAE,UAAU,MAAM,MAAM,UAAU,aAAa,KAAK,CAAC;AAC9E,iBAAW,CAAC,SAAS,CAAC,GAAG,MAAM,MAAM,CAAC;AACtC,wBAAkB,QAAQ;AAC1B,qBAAe,KAAK;AAAA,IACtB;AAAA,IACA,CAAC,MAAM,MAAM;AAAA,EACf;AAEA,QAAM,WAAW;AAAA,IACf,CAAC,UAAkB,SAAiB;AAClC,UAAI,CAAC,KAAM;AACX;AAAA,QAAW,CAAC,SACV,KAAK;AAAA,UAAI,CAAC,MACR,EAAE,OAAO,WACL,EAAE,GAAG,GAAG,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,WAAW,GAAG,UAAU,QAAQ,MAAM,MAAM,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC,EAAE,IAC3H;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,IAAI;AAAA,EACP;AAEA,QAAM,gBAAgB,YAAY,CAAC,aAAqB;AACtD,eAAW,CAAC,SAAS,KAAK,IAAI,CAAC,MAAM,EAAE,OAAO,WAAW,EAAE,GAAG,GAAG,UAAU,CAAC,EAAE,SAAS,IAAI,CAAC,CAAC;AAC7F,sBAAkB,IAAI;AAAA,EACxB,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,YAAY,CAAC,aAAqB;AACrD,eAAW,CAAC,SAAS,KAAK,OAAO,CAAC,MAAM,EAAE,OAAO,QAAQ,CAAC;AAC1D,sBAAkB,IAAI;AAAA,EACxB,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAkB,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE;AAE3D,SACE,oBAAC,eAAe,UAAf,EAAwB,OAAO;AAAA,IAC9B;AAAA,IAAS;AAAA,IAAM;AAAA,IAAa;AAAA,IAAgB;AAAA,IAC5C;AAAA,IAAgB;AAAA,IAAmB;AAAA,IACnC;AAAA,IAAW;AAAA,IAAU;AAAA,IAAe;AAAA,IAAc;AAAA,IAClD;AAAA,EACF,GACG,UACH;AAEJ;AAEO,SAAS,aAAa;AAC3B,QAAM,MAAM,WAAW,cAAc;AACrC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,gDAAgD;AAC1E,SAAO;AACT;;;AG1JO,SAAS,cAAc;AAC5B,QAAM,EAAE,SAAS,WAAW,UAAU,eAAe,cAAc,gBAAgB,IAAI,WAAW;AAElG,SAAO;AAAA,IACL;AAAA,IACA,aAAa,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ;AAAA,IAC9C,iBAAiB,QAAQ,OAAO,CAAC,MAAM,EAAE,QAAQ;AAAA,IACjD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACbO,SAAS,iBAAiB;AAC/B,QAAM,EAAE,aAAa,gBAAgB,aAAa,eAAe,IAAI,WAAW;AAEhF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,mBAAmB,MAAM,eAAe,CAAC,WAAW;AAAA,IACpD;AAAA,IACA;AAAA,IACA,eAAe,MAAM,eAAe,CAAC,WAAW;AAAA,EAClD;AACF;;;ACbA,SAAS,YAAAA,WAAU,eAAAC,cAAa,UAAAC,SAAQ,aAAAC,kBAAiB;;;ACAzD,SAAS,YAAAC,WAAU,aAAAC,YAAW,eAAAC,oBAAmC;AACjE,SAAS,oBAAoB;AAyFzB,SACE,OAAAC,MADF;AAjFJ,SAAS,kBAAkB,UAAsC;AAE/D,MAAI;AACF,UAAM,KAAK,SAAS,cAAc,QAAQ;AAC1C,QAAI,cAAc,YAAa,QAAO;AAAA,EACxC,QAAQ;AAAA,EAER;AAGA,MAAI;AACF,UAAM,KAAK,SAAS,cAAc,yBAAyB,IAAI,OAAO,QAAQ,CAAC,IAAI;AACnF,QAAI,cAAc,YAAa,QAAO;AAAA,EACxC,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAMA,SAAS,uBACP,QACA,WACsC;AACtC,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,cAAc,UAAU,sBAAsB;AACpD,SAAO;AAAA,IACL,MAAO,OAAO,OAAO,MAAO,YAAY;AAAA,IACxC,KAAM,OAAO,OAAO,MAAO,YAAY;AAAA,EACzC;AACF;AAMA,SAAS,gBACP,QACA,WACsC;AACtC,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,cAAc,UAAU,sBAAsB;AAEpD,MAAI,OAAO,UAAU;AACnB,UAAM,SAAS,kBAAkB,OAAO,QAAQ;AAChD,QAAI,QAAQ;AACV,YAAM,aAAa,OAAO,sBAAsB;AAChD,aAAO;AAAA,QACL,MAAM,WAAW,OAAO,YAAY,OAAQ,OAAO,OAAO,MAAO,WAAW;AAAA,QAC5E,KAAK,WAAW,MAAM,YAAY,MAAO,OAAO,OAAO,MAAO,WAAW;AAAA,MAC3E;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAO,OAAO,OAAO,MAAO,YAAY;AAAA,IACxC,KAAM,OAAO,OAAO,MAAO,YAAY;AAAA,EACzC;AACF;AAIA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,cAAc,OAAO,SAAS,CAAC,GAAG,OAAO,SAAS;AAExD,SACE,qBAAC,YAAO,SAAkB,WAAU,SAAQ,OAAO,EAAE,UAAU,WAAW,GACxE;AAAA,oBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,YAKP,WAAW,8CAA8C,iBAAiB;AAAA,YAC1E,OAAO,WAAW,eAAe,EAAE;AAAA;AAAA,QAEvC,OAAO,EAAE,iBAAiB,YAAY;AAAA,QAErC,kBAAQ;AAAA;AAAA,IACX;AAAA,IACC,CAAC,OAAO,YAAY,CAAC,YACpB,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO,EAAE,iBAAiB,YAAY;AAAA;AAAA,IACxC;AAAA,IAED,OAAO,eACN,gBAAAA,KAAC,SAAI,WAAU,yPAGZ,iBAAO,aACV;AAAA,KAEJ;AAEJ;AAIA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AACF,GAGG;AACD,QAAM,EAAE,gBAAgB,kBAAkB,IAAI,WAAW;AACzD,QAAM,WAAW,mBAAmB,OAAO;AAC3C,QAAM,CAAC,UAAU,WAAW,IAAIC,UAA6B,IAAI;AAEjE,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,OAAO,SAAU;AAEtB,aAAS,UAAU;AACjB,YAAM,KAAK,kBAAkB,OAAO,QAAS;AAC7C,UAAI,IAAI;AACN,cAAM,MAAM,iBAAiB,EAAE,EAAE;AACjC,YAAI,QAAQ,SAAU,IAAG,MAAM,WAAW;AAC1C,oBAAY,EAAE;AAAA,MAChB,OAAO;AACL,oBAAY,IAAI;AAAA,MAClB;AAAA,IACF;AAEA,YAAQ;AAGR,UAAM,WAAW,IAAI,iBAAiB,MAAM,QAAQ,CAAC;AACrD,aAAS,QAAQ,SAAS,MAAM,EAAE,WAAW,MAAM,SAAS,KAAK,CAAC;AAElE,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEpB,MAAI,CAAC,SAAU,QAAO;AAEtB,SAAO;AAAA,IACL,gBAAAF;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO;AAAA,UACL,MAAM,GAAG,OAAO,IAAI;AAAA,UACpB,KAAK,GAAG,OAAO,IAAI;AAAA,UACnB,WAAW;AAAA,UACX,QAAQ;AAAA,QACV;AAAA,QAEA,0BAAAA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA,SAAS,CAAC,MAAM;AACd,gBAAE,gBAAgB;AAClB,gCAAkB,WAAW,OAAO,OAAO,EAAE;AAAA,YAC/C;AAAA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;AAIA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,EAAE,gBAAgB,kBAAkB,IAAI,WAAW;AACzD,QAAM,WAAW,mBAAmB,OAAO;AAC3C,QAAM,CAAC,KAAK,MAAM,IAAIC,UAA+C,IAAI;AAEzE,QAAM,YAAYE,aAAY,MAAM;AAClC,WAAO,uBAAuB,QAAQ,WAAW,OAAO,CAAC;AAAA,EAC3D,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,EAAAD,WAAU,MAAM;AACd,cAAU;AACV,WAAO,iBAAiB,UAAU,SAAS;AAC3C,WAAO,MAAM,OAAO,oBAAoB,UAAU,SAAS;AAAA,EAC7D,GAAG,CAAC,SAAS,CAAC;AAEd,MAAI,CAAC,IAAK,QAAO;AAEjB,SACE,gBAAAF;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO;AAAA,QACL,MAAM,IAAI;AAAA,QACV,KAAK,IAAI;AAAA,QACT,WAAW;AAAA,QACX,QAAQ;AAAA,MACV;AAAA,MAEA,0BAAAA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS,CAAC,MAAM;AACd,cAAE,gBAAgB;AAClB,8BAAkB,WAAW,OAAO,OAAO,EAAE;AAAA,UAC/C;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;AAIO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,OAAO,UAAU;AACnB,WAAO,gBAAAA,KAAC,eAA0C,QAAgB,SAAzC,YAAY,OAAO,EAAE,EAAkC;AAAA,EAClF;AACA,SAAO,gBAAAA,KAAC,cAAwC,QAAgB,OAAc,cAAtD,WAAW,OAAO,EAAE,EAA0D;AACxG;;;ACxPA,SAAS,aAAAI,YAAW,UAAAC,SAAQ,YAAAC,WAAU,eAAAC,oBAAmC;AACzE,SAAS,OAAO,QAAQ,aAAa;;;ACDrC,SAAS,YAAAC,WAAU,UAAAC,SAAQ,aAAAC,kBAAiB;AAC5C,SAAS,YAAY;AA4BjB,SACE,OAAAC,MADF,QAAAC,aAAA;AA1BG,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,cAAc;AAAA,EACd,YAAY;AACd,GAIG;AACD,QAAM,CAAC,OAAO,QAAQ,IAAIJ,UAAS,EAAE;AACrC,QAAM,WAAWC,QAA4B,IAAI;AAEjD,EAAAC,WAAU,MAAM;AACd,QAAI,WAAW;AACb,eAAS,SAAS,MAAM;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,eAAe,MAAM;AACzB,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAS;AACd,aAAS,OAAO;AAChB,aAAS,EAAE;AAAA,EACb;AAEA,SACE,gBAAAE,MAAC,SAAI,WAAU,wBACb;AAAA,oBAAAD;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL;AAAA,QACA,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,QACxC,WAAW,CAAC,MAAM;AAChB,cAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AACpC,cAAE,eAAe;AACjB,yBAAa;AAAA,UACf;AAAA,QACF;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,WAAU;AAAA;AAAA,IAGZ;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,UAAU,CAAC,MAAM,KAAK;AAAA,QACtB,WAAU;AAAA,QAIV,0BAAAA,KAAC,QAAK,WAAU,eAAc;AAAA;AAAA,IAChC;AAAA,KACF;AAEJ;;;ADkBY,SAIE,OAAAE,MAJF,QAAAC,aAAA;AApEZ,SAAS,QAAQ,KAAqB;AACpC,QAAM,OAAO,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,EAAE,QAAQ;AAChD,QAAM,OAAO,KAAK,MAAM,OAAO,GAAK;AACpC,MAAI,OAAO,EAAG,QAAO;AACrB,MAAI,OAAO,GAAI,QAAO,GAAG,IAAI;AAC7B,QAAM,QAAQ,KAAK,MAAM,OAAO,EAAE;AAClC,MAAI,QAAQ,GAAI,QAAO,GAAG,KAAK;AAC/B,QAAM,OAAO,KAAK,MAAM,QAAQ,EAAE;AAClC,SAAO,GAAG,IAAI;AAChB;AAEO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGG;AACD,QAAM,EAAE,gBAAgB,mBAAmB,UAAU,eAAe,cAAc,KAAK,IACrF,WAAW;AACb,QAAM,MAAMC,QAAuB,IAAI;AACvC,QAAM,SAAS,mBAAmB,OAAO;AAEzC,QAAM,CAAC,KAAK,MAAM,IAAIC,UAA+C,IAAI;AAEzE,QAAM,YAAYC,aAAY,MAAM;AAClC,WAAO,gBAAgB,QAAQ,WAAW,OAAO,CAAC;AAAA,EACpD,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,cAAU;AACV,WAAO,iBAAiB,UAAU,SAAS;AAE3C,aAAS,iBAAiB,UAAU,WAAW,IAAI;AACnD,WAAO,MAAM;AACX,aAAO,oBAAoB,UAAU,SAAS;AAC9C,eAAS,oBAAoB,UAAU,WAAW,IAAI;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,QAAQ,SAAS,CAAC;AAEtB,EAAAA,WAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,UAAU,CAAC,MAAkB;AACjC,UAAI,IAAI,WAAW,CAAC,IAAI,QAAQ,SAAS,EAAE,MAAc,GAAG;AAC1D,0BAAkB,IAAI;AAAA,MACxB;AAAA,IACF;AACA,UAAM,QAAQ,WAAW,MAAM,SAAS,iBAAiB,aAAa,OAAO,GAAG,CAAC;AACjF,WAAO,MAAM;AACX,mBAAa,KAAK;AAClB,eAAS,oBAAoB,aAAa,OAAO;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,QAAQ,iBAAiB,CAAC;AAE9B,MAAI,CAAC,UAAU,CAAC,IAAK,QAAO;AAE5B,SACE,gBAAAL;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MACV,OAAO,EAAE,MAAM,IAAI,MAAM,KAAK,IAAI,IAAI;AAAA,MACtC,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA,MAElC,0BAAAC,MAAC,SAAI,WAAU,iFAEb;AAAA,wBAAAA,MAAC,SAAI,WAAU,2FACb;AAAA,0BAAAA,MAAC,SAAI,WAAU,2BACb;AAAA,4BAAAA,MAAC,UAAK,WAAU,wCACb;AAAA,qBAAO,SAAS;AAAA,cAAO;AAAA,cAAE,OAAO,SAAS,WAAW,IAAI,YAAY;AAAA,eACvE;AAAA,YACC,OAAO,eACN,gBAAAD,KAAC,UAAK,WAAU,0EACb,iBAAO,aACV;AAAA,YAED,OAAO,YACN,gBAAAA,KAAC,UAAK,WAAU,4CAA2C,sBAAQ;AAAA,aAEvE;AAAA,UACA,gBAAAC,MAAC,SAAI,WAAU,cACb;AAAA,4BAAAD;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,MAAM,cAAc,OAAO,EAAE;AAAA,gBACtC,WAAU;AAAA,gBACV,OAAO,OAAO,WAAW,WAAW;AAAA,gBAEnC,iBAAO,WACN,gBAAAA,KAAC,SAAM,WAAU,gCAA+B,IAEhD,gBAAAA,KAAC,SAAM,WAAU,gCAA+B;AAAA;AAAA,YAEpD;AAAA,YACA,gBAAAA;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,MAAM,aAAa,OAAO,EAAE;AAAA,gBACrC,WAAU;AAAA,gBACV,OAAM;AAAA,gBAEN,0BAAAA,KAAC,UAAO,WAAU,mDAAkD;AAAA;AAAA,YACtE;AAAA,aACF;AAAA,WACF;AAAA,QAGA,gBAAAA,KAAC,SAAI,WAAU,4BACZ,iBAAO,SAAS,IAAI,CAAC,YACpB,gBAAAC,MAAC,SAAqB,WAAU,sDAC9B;AAAA,0BAAAA,MAAC,SAAI,WAAU,gCACb;AAAA,4BAAAD;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO,EAAE,iBAAiB,QAAQ,OAAO,MAAM;AAAA,gBAE9C,kBAAQ,OAAO,KAAK,CAAC,GAAG,YAAY;AAAA;AAAA,YACvC;AAAA,YACA,gBAAAA,KAAC,UAAK,WAAU,wCACb,kBAAQ,OAAO,MAClB;AAAA,YACA,gBAAAA,KAAC,UAAK,WAAU,wCACb,kBAAQ,QAAQ,SAAS,GAC5B;AAAA,aACF;AAAA,UACA,gBAAAA,KAAC,OAAE,WAAU,iDACV,kBAAQ,MACX;AAAA,aAjBQ,QAAQ,EAkBlB,CACD,GACH;AAAA,QAGC,QAAQ,CAAC,OAAO,YACf,gBAAAA,KAAC,SAAI,WAAU,4DACb,0BAAAA;AAAA,UAAC;AAAA;AAAA,YACC,UAAU,CAAC,SAAS,SAAS,OAAO,IAAI,IAAI;AAAA,YAC5C,aAAY;AAAA,YACZ,WAAS;AAAA;AAAA,QACX,GACF;AAAA,SAEJ;AAAA;AAAA,EACF;AAEJ;;;AEnJA,SAAS,YAAAM,iBAAgB;AAkBnB,SACE,OAAAC,MADF,QAAAC,aAAA;AAfC,SAAS,aAAa;AAC3B,QAAM,EAAE,MAAM,SAAS,YAAY,IAAI,WAAW;AAClD,QAAM,CAAC,MAAM,OAAO,IAAIC,UAAS,EAAE;AAGnC,MAAI,QAAQ,CAAC,YAAa,QAAO;AAEjC,QAAM,eAAe,MAAM;AACzB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AACd,YAAQ,OAAO;AAAA,EACjB;AAEA,SACE,gBAAAF,KAAC,SAAI,WAAU,4FACb,0BAAAC,MAAC,SAAI,WAAU,qEACb;AAAA,oBAAAD,KAAC,QAAG,WAAU,+CAA8C,+BAE5D;AAAA,IACA,gBAAAA,KAAC,OAAE,WAAU,iCAAgC,oDAE7C;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,QACvC,WAAW,CAAC,MAAM;AAChB,cAAI,EAAE,QAAQ,QAAS,cAAa;AAAA,QACtC;AAAA,QACA,aAAY;AAAA,QACZ,WAAS;AAAA,QACT,WAAU;AAAA;AAAA,IAEZ;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,UAAU,CAAC,KAAK,KAAK;AAAA,QACrB,WAAU;AAAA,QAEX;AAAA;AAAA,IAED;AAAA,KACF,GACF;AAEJ;;;AJoTI,mBACE,OAAAG,MAeI,QAAAC,aAhBN;AAnVJ,IAAI,cAAc;AAClB,IAAI,iBAAiB;AACrB,SAAS,mBAA2B;AAClC,QAAM,MAAM,KAAK,IAAI;AACrB,MAAI,MAAM,iBAAiB,IAAK,QAAO;AACvC,MAAI,MAAM;AACV,QAAM,MAAM,SAAS,iBAAiB,mCAAmC;AACzE,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAM,IAAI,SAAS,iBAAiB,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE;AACtD,QAAI,CAAC,MAAM,CAAC,KAAK,IAAI,IAAK,OAAM;AAAA,EAClC;AACA,gBAAc,KAAK,IAAI,KAAK,GAAG;AAC/B,mBAAiB;AACjB,SAAO;AACT;AAGA,IAAM,gBAAgB,oBAAI,IAAI;AAAA,EAC5B;AAAA,EAAW;AAAA,EAAO;AAAA,EAAS;AAAA,EAAU;AAAA,EAAU;AAAA,EAC/C;AAAA,EAAW;AAAA,EAAQ;AAAA,EAAU;AAC/B,CAAC;AAGD,IAAM,kBAAkB;AACxB,IAAM,qBAAqB;AAM3B,SAAS,cAAc,IAA0B;AAC/C,QAAM,QAAQ,iBAAiB,EAAE;AAGjC,MAAI,cAAc,KAAK,MAAM,WAAW,MAAM,YAAY,MAAM,SAAS,EAAG,QAAO;AAGnF,MAAI,MAAM,eAAe,MAAM,gBAAgB,SAAS,MAAM,gBAAgB,OAAQ,QAAO;AAC7F,MAAI,MAAM,gBAAgB,MAAM,iBAAiB,MAAO,QAAO;AAC/D,MAAI,MAAM,mBAAmB,MAAM,oBAAoB,sBAAsB,MAAM,oBAAoB,cAAe,QAAO;AAC7H,MAAI,MAAM,aAAa,MAAM,cAAc,OAAQ,QAAO;AAE1D,SAAO;AACT;AAMA,SAAS,WAAW,IAAgC;AAElD,QAAM,UAAU,GAAG,cAAc,8DAA8D;AAC/F,MAAI,SAAS;AACX,UAAM,OAAO,QAAQ,aAAa,KAAK;AACvC,QAAI,QAAQ,KAAK,UAAU,GAAI,QAAO;AAAA,EACxC;AAGA,QAAM,YAAY,GAAG,cAAc,wBAAwB;AAC3D,MAAI,WAAW;AACb,UAAM,OAAO,UAAU,aAAa,KAAK;AACzC,QAAI,QAAQ,KAAK,UAAU,GAAI,QAAO;AAAA,EACxC;AAEA,SAAO;AACT;AAKA,SAAS,aAAa,IAAyB;AAC7C,QAAM,SAAS,GAAG,aAAa,qBAAqB;AACpD,MAAI,OAAQ,QAAO;AAEnB,MAAI,GAAG,GAAI,QAAO,IAAI,GAAG,EAAE;AAE3B,QAAM,QAAQ,GAAG,aAAa,YAAY;AAC1C,MAAI,MAAO,QAAO,GAAG,GAAG,QAAQ,YAAY,CAAC,gBAAgB,KAAK;AAGlE,QAAM,QAAkB,CAAC;AACzB,MAAI,MAA0B;AAC9B,WAAS,QAAQ,GAAG,OAAO,QAAQ,GAAG,SAAS;AAC7C,QAAI,IAAI,IAAI;AACV,YAAM,QAAQ,IAAI,IAAI,EAAE,EAAE;AAC1B;AAAA,IACF;AACA,UAAM,IAAwB,IAAI;AAClC,QAAI,GAAG;AACL,YAAM,WAAW,MAAM,KAAK,EAAE,QAAQ;AACtC,YAAM,MAAM,SAAS,QAAQ,GAAG;AAChC,YAAM,QAAQ,GAAG,IAAI,QAAQ,YAAY,CAAC,cAAc,MAAM,CAAC,GAAG;AAAA,IACpE,OAAO;AACL,YAAM,QAAQ,IAAI,QAAQ,YAAY,CAAC;AAAA,IACzC;AACA,UAAM;AAAA,EACR;AACA,SAAO,MAAM,KAAK,KAAK;AACzB;AAKA,SAAS,gBAAgB,IAAyB;AAChD,QAAM,SAAS,GAAG,aAAa,oBAAoB;AACnD,MAAI,OAAQ,QAAO;AAEnB,QAAM,YAAY,GAAG,aAAa,YAAY;AAC9C,MAAI,UAAW,QAAO;AAGtB,QAAM,WAAW,WAAW,EAAE;AAC9B,MAAI,SAAU,QAAO;AAErB,QAAM,OAAO,GAAG,aAAa,MAAM;AAEnC,MAAI,GAAG,IAAI;AACT,UAAM,SAAS,GAAG,GACf,QAAQ,SAAS,GAAG,EACpB,QAAQ,mBAAmB,OAAO,EAClC,QAAQ,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC;AAC1C,WAAO,OAAO,GAAG,MAAM,KAAK,IAAI,MAAM;AAAA,EACxC;AAEA,QAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,MAAI,cAAc,IAAI,GAAG,OAAO,EAAG,QAAO,OAAO,GAAG,GAAG,KAAK,IAAI,MAAM;AACtE,MAAI,KAAM,QAAO;AAEjB,SAAO;AACT;AAMA,SAAS,kBAAkB,IAAiB,UAA8B;AACxE,MAAI,UAA8B;AAClC,QAAM,aAAkE,CAAC;AACzE,MAAI,QAAQ;AAEZ,SAAO,WAAW,YAAY,YAAY,YAAY,SAAS,MAAM;AACnE,UAAM,OAAO,QAAQ,sBAAsB;AAC3C,UAAM,cAAc,KAAK,SAAS,mBAAmB,KAAK,UAAU;AACpE,UAAM,WACJ,KAAK,QAAQ,OAAO,aAAa,sBACjC,KAAK,SAAS,OAAO,cAAc;AAErC,QAAI,eAAe,CAAC,UAAU;AAC5B,UAAI,QAAQ;AAGZ,UAAI,QAAQ,aAAa,qBAAqB,EAAG,SAAQ;AAAA,eAEhD,QAAQ,GAAI,SAAQ;AAAA,eAEpB,QAAQ,aAAa,YAAY,EAAG,SAAQ;AAAA,eAE5C,QAAQ,aAAa,MAAM,EAAG,SAAQ;AAAA,eAEtC,cAAc,IAAI,QAAQ,OAAO,EAAG,SAAQ;AAAA,eAE5C,cAAc,OAAO,EAAG,SAAQ;AAEzC,UAAI,QAAQ,GAAG;AACb,mBAAW,KAAK,EAAE,IAAI,SAAS,OAAO,MAAM,CAAC;AAAA,MAC/C;AAAA,IACF;AAEA,cAAU,QAAQ;AAClB;AAAA,EACF;AAEA,MAAI,WAAW,WAAW,EAAG,QAAO;AAIpC,aAAW,KAAK,CAAC,GAAG,MAAM;AACxB,UAAM,SAAS,EAAE,QAAQ,KAAK,IAAI,GAAG,KAAK,EAAE,KAAK;AACjD,UAAM,SAAS,EAAE,QAAQ,KAAK,IAAI,GAAG,KAAK,EAAE,KAAK;AACjD,WAAO,SAAS;AAAA,EAClB,CAAC;AAED,QAAM,OAAO,WAAW,CAAC;AACzB,QAAM,WAAW,aAAa,KAAK,EAAE;AACrC,QAAM,cAAc,gBAAgB,KAAK,EAAE;AAE3C,QAAM,IAAI,gBAAgB,WAAW,IAAI,CAAC,OAAO;AAAA,IAC/C,KAAK,EAAE,GAAG,QAAQ,YAAY;AAAA,IAC9B,IAAI,EAAE,GAAG,MAAM;AAAA,IACf,OAAO,EAAE,GAAG,WAAW,SAAS,EAAE,MAAM,GAAG,EAAE,KAAK;AAAA,IAClD,OAAO,EAAE;AAAA,IACT,OAAO,EAAE;AAAA,IACT,OAAO,gBAAgB,EAAE,EAAE;AAAA,EAC7B,EAAE,CAAC;AAEH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,KAAK;AAAA,EAChB;AACF;AAEO,SAAS,iBAAiB;AAC/B,QAAM,EAAE,SAAS,aAAa,gBAAgB,MAAM,WAAW,kBAAkB,IAC/E,WAAW;AACb,QAAM,aAAaC,QAAuB,IAAI;AAC9C,QAAM,CAAC,YAAY,aAAa,IAAIC,UAA4B,IAAI;AACpE,QAAM,CAAC,cAAc,eAAe,IAAIA,UAA+C,IAAI;AAE3F,QAAM,cAAcC;AAAA,IAClB,CAAC,MAAwC;AACvC,UAAI,CAAC,eAAe,CAAC,WAAW,QAAS;AAEzC,YAAM,cAAc,WAAW,QAAQ,sBAAsB;AAG7D,YAAM,UAAU,WAAW;AAC3B,cAAQ,MAAM,gBAAgB;AAC9B,YAAM,eAAe,SAAS,iBAAiB,EAAE,SAAS,EAAE,OAAO;AACnE,cAAQ,MAAM,gBAAgB;AAE9B,YAAM,IAAI,aAAa,EAAE,SAAS,EAAE,SAAS,SAAS,EAAE,QAAQ,CAAC;AACjE,YAAM,IAAI,2BAA2B,YAAY;AAGjD,UAAI,cAAc;AAChB,cAAM,OAAiB,CAAC;AACxB,YAAI,OAA2B;AAC/B,eAAO,QAAQ,SAAS,SAAS,MAAM;AACrC,gBAAM,QAAkB,CAAC,KAAK,QAAQ,YAAY,CAAC;AACnD,cAAI,KAAK,GAAI,OAAM,KAAK,IAAI,KAAK,EAAE,EAAE;AACrC,cAAI,KAAK,aAAa,qBAAqB,EAAG,OAAM,KAAK,yBAAyB,KAAK,aAAa,qBAAqB,CAAC,IAAI;AAC9H,cAAI,KAAK,aAAa,YAAY,EAAG,OAAM,KAAK,gBAAgB,KAAK,aAAa,YAAY,CAAC,IAAI;AACnG,cAAI,KAAK,aAAa,MAAM,EAAG,OAAM,KAAK,UAAU,KAAK,aAAa,MAAM,CAAC,IAAI;AACjF,cAAI,cAAc,IAAI,KAAK,OAAO,EAAG,OAAM,KAAK,YAAY;AAC5D,eAAK,KAAK,MAAM,KAAK,EAAE,CAAC;AACxB,iBAAO,KAAK;AAAA,QACd;AACA,cAAM,IAAI,cAAc,KAAK,KAAK,UAAK,CAAC;AAAA,MAC1C;AAEA,YAAM,SAAS,eAAe,kBAAkB,cAAc,WAAW,OAAO,IAAI;AAEpF,UAAI,QAAQ;AACV,cAAM,aAAa,OAAO,QAAQ,sBAAsB;AACxD,cAAM,KAAM,EAAE,UAAU,WAAW,QAAQ,WAAW,QAAS;AAC/D,cAAM,KAAM,EAAE,UAAU,WAAW,OAAO,WAAW,SAAU;AAC/D,cAAM,IAAI,yBAAoB;AAAA,UAC5B,UAAU,OAAO;AAAA,UACjB,aAAa,OAAO;AAAA,UACpB,SAAS,OAAO;AAAA,UAChB,aAAa,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,GAAG,EAAE,QAAQ,CAAC,EAAE;AAAA,UAChD,YAAY,EAAE,GAAG,WAAW,OAAO,GAAG,WAAW,OAAO;AAAA,QAC1D,CAAC;AACD,sBAAc,EAAE,GAAG,GAAG,UAAU,OAAO,UAAU,aAAa,OAAO,YAAY,CAAC;AAAA,MACpF,OAAO;AACL,cAAM,KAAM,EAAE,UAAU,YAAY,QAAQ,YAAY,QAAS;AACjE,cAAM,KAAM,EAAE,UAAU,YAAY,OAAO,YAAY,SAAU;AACjE,cAAM,IAAI,mEAAyD;AAAA,UACjE,GAAG,EAAE,QAAQ,CAAC;AAAA,UACd,GAAG,EAAE,QAAQ,CAAC;AAAA,QAChB,CAAC;AACD,sBAAc,EAAE,GAAG,EAAE,CAAC;AAAA,MACxB;AAEA,sBAAgB;AAAA,QACd,MAAM,EAAE,UAAU,YAAY;AAAA,QAC9B,KAAK,EAAE,UAAU,YAAY;AAAA,MAC/B,CAAC;AACD,wBAAkB,IAAI;AAAA,IACxB;AAAA,IACA,CAAC,aAAa,iBAAiB;AAAA,EACjC;AAEA,QAAM,mBAAmBA;AAAA,IACvB,CAAC,SAAiB;AAChB,UAAI,CAAC,WAAY;AACjB,YAAM,IAAI,mBAAmB;AAAA,QAC3B,MAAM,WAAW,EAAE,QAAQ,CAAC;AAAA,QAC5B,MAAM,WAAW,EAAE,QAAQ,CAAC;AAAA,QAC5B,UAAU,WAAW,YAAY;AAAA,QACjC,aAAa,WAAW,eAAe;AAAA,QACvC;AAAA,MACF,CAAC;AACD,gBAAU,WAAW,GAAG,WAAW,GAAG,MAAM,WAAW,UAAU,WAAW,WAAW;AACvF,oBAAc,IAAI;AAClB,sBAAgB,IAAI;AAAA,IACtB;AAAA,IACA,CAAC,YAAY,SAAS;AAAA,EACxB;AAEA,QAAM,gBAAgBA,aAAY,MAAM;AACtC,kBAAc,IAAI;AAClB,oBAAgB,IAAI;AACpB,mBAAe,KAAK;AAAA,EACtB,GAAG,CAAC,cAAc,CAAC;AAGnB,EAAAC,WAAU,MAAM;AACd,UAAM,UAAU,CAAC,MAAqB;AACpC,YAAM,MAAO,EAAE,QAAwB;AAGvC,UAAI,EAAE,QAAQ,UAAU;AACtB,YAAI,YAAY;AACd,wBAAc,IAAI;AAClB,0BAAgB,IAAI;AACpB,yBAAe,KAAK;AAAA,QACtB,WAAW,aAAa;AACtB,yBAAe,KAAK;AAAA,QACtB;AACA;AAAA,MACF;AAGA,UAAI,QAAQ,WAAW,QAAQ,WAAY;AAE3C,UAAI,EAAE,QAAQ,OAAO,EAAE,QAAQ,KAAK;AAClC,YAAI,CAAC,aAAa;AAChB,gBAAM,IAAI,kBAAkB;AAC5B,4BAAkB,IAAI;AACtB,yBAAe,IAAI;AAAA,QACrB,OAAO;AACL,gBAAM,IAAI,mBAAmB;AAC7B,yBAAe,KAAK;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AACA,aAAS,iBAAiB,WAAW,OAAO;AAC5C,WAAO,MAAM,SAAS,oBAAoB,WAAW,OAAO;AAAA,EAC9D,GAAG,CAAC,aAAa,YAAY,gBAAgB,iBAAiB,CAAC;AAG/D,QAAM,gBAAgB,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM;AAChD,QAAI,EAAE,aAAa,EAAE,SAAU,QAAO,EAAE,WAAW,IAAI;AACvD,WAAO,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ;AAAA,EACzE,CAAC;AAED,SACE,gBAAAJ,MAAA,YACE;AAAA,oBAAAD,KAAC,cAAW;AAAA,IAGZ,gBAAAC;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,WAAW,iBACT,cACI,yCACA,qBACN;AAAA,QACA,OAAO,EAAE,QAAQ,cAAc,iBAAiB,IAAI,KAAK,GAAG;AAAA,QAC5D,aAAa;AAAA,QAGZ;AAAA,wBAAc,IAAI,CAAC,QAAQ,MAC1B,gBAAAA,MAAC,SAAoB,WAAU,uBAC7B;AAAA,4BAAAD,KAAC,cAAW,QAAgB,OAAO,GAAG,YAAwB;AAAA,YAC9D,gBAAAA,KAAC,wBAAqB,QAAgB,YAAwB;AAAA,eAFtD,OAAO,EAGjB,CACD;AAAA,UAGA,cAAc,gBAAgB,QAC7B,gBAAAC;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO;AAAA,gBACL,MAAM,aAAa;AAAA,gBACnB,KAAK,aAAa;AAAA,cACpB;AAAA,cACA,aAAa,CAAC,MAAM,EAAE,gBAAgB;AAAA,cACtC,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA,cAElC;AAAA,gCAAAD;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAU;AAAA,oBAGV,OAAO,EAAE,iBAAiB,KAAK,MAAM;AAAA,oBACtC;AAAA;AAAA,gBAED;AAAA,gBACA,gBAAAA,KAAC,SAAI,WAAU,4BACb,0BAAAC,MAAC,SAAI,WAAU,gEACb;AAAA,kCAAAA,MAAC,SAAI,WAAU,gCACb;AAAA,oCAAAD,KAAC,OAAE,WAAU,4BAA2B,yBAAW;AAAA,oBAClD,WAAW,eACV,gBAAAA,KAAC,UAAK,WAAU,0EACb,qBAAW,aACd;AAAA,qBAEJ;AAAA,kBACA,gBAAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,UAAU;AAAA,sBACV,aAAY;AAAA,sBACZ,WAAS;AAAA;AAAA,kBACX;AAAA,kBACA,gBAAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,SAAS;AAAA,sBACT,WAAU;AAAA,sBACX;AAAA;AAAA,kBAED;AAAA,mBACF,GACF;AAAA;AAAA;AAAA,UACF;AAAA;AAAA;AAAA,IAEJ;AAAA,IAGC,eAAe,CAAC,cACf,gBAAAA,KAAC,SAAI,WAAU,0EACb,0BAAAA,KAAC,SAAI,WAAU,gFAA+E,6CAE9F,GACF;AAAA,KAEJ;AAEJ;;;AKjbA,SAAS,eAAe,MAAM,SAAS;AA0B/B,gBAAAM,MAIF,QAAAC,aAJE;AAvBD,SAAS,gBAAgB;AAC9B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,WAAW;AAEf,SACE,gBAAAA,MAAC,SAAI,WAAU,kEAEb;AAAA,oBAAAD;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,MAAM,eAAe,CAAC,WAAW;AAAA,QAC1C,WAAW;AAAA;AAAA,sBAEG,cACE,8BACA,yEACJ;AAAA,QACZ,OAAM;AAAA,QAEN,0BAAAA,KAAC,QAAK,WAAU,WAAU;AAAA;AAAA,IAC5B;AAAA,IAGA,gBAAAC;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,MAAM;AACb,cAAI,aAAa;AACf,2BAAe,KAAK;AAAA,UACtB,OAAO;AACL,8BAAkB,IAAI;AACtB,2BAAe,IAAI;AAAA,UACrB;AAAA,QACF;AAAA,QACA,WAAW;AAAA;AAAA,sBAEG,cACE,sDACA,yEACJ;AAAA,QACZ,OAAO,cAAc,sBAAsB;AAAA,QAE1C;AAAA,wBACC,gBAAAD,KAAC,KAAE,WAAU,WAAU,IAEvB,gBAAAA,KAAC,iBAAc,WAAU,WAAU;AAAA,UAGpC,kBAAkB,KAAK,CAAC,eACvB,gBAAAA,KAAC,UAAK,WAAU,6MAGb,2BACH;AAAA;AAAA;AAAA,IAEJ;AAAA,KACF;AAEJ;;;AC/DA,SAAS,YAAAE,WAAU,aAAAC,kBAAiB;AACpC,SAAS,KAAAC,IAAG,SAAAC,QAAO,SAAAC,QAAO,iBAAAC,gBAAe,OAAO,gBAAgB;AA+DxD,SA0FJ,YAAAC,WAzFM,OAAAC,MADF,QAAAC,aAAA;AA3DR,SAASC,SAAQ,KAAqB;AACpC,QAAM,OAAO,KAAK,IAAI,IAAI,IAAI,KAAK,GAAG,EAAE,QAAQ;AAChD,QAAM,OAAO,KAAK,MAAM,OAAO,GAAK;AACpC,MAAI,OAAO,EAAG,QAAO;AACrB,MAAI,OAAO,GAAI,QAAO,GAAG,IAAI;AAC7B,QAAM,QAAQ,KAAK,MAAM,OAAO,EAAE;AAClC,MAAI,QAAQ,GAAI,QAAO,GAAG,KAAK;AAC/B,QAAM,OAAO,KAAK,MAAM,QAAQ,EAAE;AAClC,SAAO,GAAG,IAAI;AAChB;AAEA,SAAS,gBAAgB,QAAwB;AAC/C,SAAO,OAAO,QAAQ,OAAO,GAAG,EAAE,QAAQ,MAAM,GAAG;AACrD;AAIO,SAAS,iBAAiB;AAC/B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,WAAW;AAEf,QAAM,CAAC,KAAK,MAAM,IAAIC,UAAyB,MAAM;AACrD,QAAM,CAAC,UAAU,WAAW,IAAIA,UAAuB,CAAC,CAAC;AACzD,QAAM,CAAC,YAAY,aAAa,IAAIA,UAAS,KAAK;AAGlD,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,eAAe,QAAQ,MAAO;AACnC,kBAAc,IAAI;AAGlB,mBAAe,WAAW;AAExB,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,cAAc;AACtC,YAAI,IAAI,IAAI;AACV,gBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,sBAAY,IAAI;AAAA,QAClB;AAAA,MACF,QAAQ;AAAA,MAAC;AACT,oBAAc,KAAK;AAAA,IACrB;AACA,aAAS;AAAA,EACX,GAAG,CAAC,aAAa,GAAG,CAAC;AAErB,MAAI,CAAC,YAAa,QAAO;AAEzB,QAAM,cAAc,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ;AACrD,QAAM,kBAAkB,QAAQ,OAAO,CAAC,MAAM,EAAE,QAAQ;AAExD,SACE,gBAAAH,MAAC,SAAI,WAAU,4GAEb;AAAA,oBAAAA,MAAC,SAAI,WAAU,2EACb;AAAA,sBAAAA,MAAC,SAAI,WAAU,2BACb;AAAA,wBAAAD,KAACK,gBAAA,EAAc,WAAU,4BAA2B;AAAA,QACpD,gBAAAL,KAAC,UAAK,WAAU,0CAAyC,sBAEzD;AAAA,SACF;AAAA,MACA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,eAAe,KAAK;AAAA,UACnC,WAAU;AAAA,UAEV,0BAAAA,KAACM,IAAA,EAAE,WAAU,4BAA2B;AAAA;AAAA,MAC1C;AAAA,OACF;AAAA,IAGA,gBAAAL,MAAC,SAAI,WAAU,oCACb;AAAA,sBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,OAAO,MAAM;AAAA,UAC5B,WAAW,8FACT,QAAQ,SACJ,mDACA,yCACN;AAAA,UAEA;AAAA,4BAAAD,KAAC,YAAS,WAAU,WAAU;AAAA,YAAE;AAAA,YAE/B,YAAY,SAAS,KACpB,gBAAAA,KAAC,UAAK,WAAU,gEACb,sBAAY,QACf;AAAA;AAAA;AAAA,MAEJ;AAAA,MACA,gBAAAC;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,OAAO,KAAK;AAAA,UAC3B,WAAW,8FACT,QAAQ,QACJ,mDACA,yCACN;AAAA,UAEA;AAAA,4BAAAD,KAAC,SAAM,WAAU,WAAU;AAAA,YAAE;AAAA;AAAA;AAAA,MAE/B;AAAA,OACF;AAAA,IAGA,gBAAAA,KAAC,SAAI,WAAU,0BACZ,kBAAQ,SACP,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV,WAAW;AAAA;AAAA,IACb,IAEA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,SAAS;AAAA;AAAA,IACX,GAEJ;AAAA,KACF;AAEJ;AAIA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,MAAI,QAAQ,WAAW,GAAG;AACxB,WACE,gBAAAA,KAAC,SAAI,WAAU,4CAA2C,uCAE1D;AAAA,EAEJ;AAEA,SACE,gBAAAC,MAAAF,WAAA,EACG;AAAA,gBAAY,SAAS,KACpB,gBAAAE,MAAC,SACC;AAAA,sBAAAA,MAAC,SAAI,WAAU,iFAAgF;AAAA;AAAA,QACtF,YAAY;AAAA,QAAO;AAAA,SAC5B;AAAA,MACC,YAAY,IAAI,CAAC,WAChB,gBAAAD;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,UAAU,MAAM,SAAS,OAAO,EAAE;AAAA,UAClC,WAAW,MAAM,UAAU,OAAO,EAAE;AAAA;AAAA,QAH/B,OAAO;AAAA,MAId,CACD;AAAA,OACH;AAAA,IAGD,gBAAgB,SAAS,KACxB,gBAAAC,MAAC,SACC;AAAA,sBAAAA,MAAC,SAAI,WAAU,iFAAgF;AAAA;AAAA,QAClF,gBAAgB;AAAA,QAAO;AAAA,SACpC;AAAA,MACC,gBAAgB,IAAI,CAAC,WACpB,gBAAAD;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,UAAU,MAAM,SAAS,OAAO,EAAE;AAAA,UAClC,WAAW,MAAM,UAAU,OAAO,EAAE;AAAA,UACpC,UAAQ;AAAA;AAAA,QAJH,OAAO;AAAA,MAKd,CACD;AAAA,OACH;AAAA,KAEJ;AAEJ;AAIA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AACF,GAGG;AACD,MAAI,SAAS;AACX,WACE,gBAAAA,KAAC,SAAI,WAAU,4CAA2C,wBAE1D;AAAA,EAEJ;AAEA,MAAI,MAAM,WAAW,GAAG;AACtB,WACE,gBAAAA,KAAC,SAAI,WAAU,4CAA2C,8CAE1D;AAAA,EAEJ;AAEA,QAAM,YAAY,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC;AAE3F,SACE,gBAAAC,MAAAF,WAAA,EACG;AAAA,gBAAY,KACX,gBAAAE,MAAC,SAAI,WAAU,iFACZ;AAAA;AAAA,MAAU;AAAA,MAAc,MAAM;AAAA,MAAO;AAAA,OACxC;AAAA,IAGD,MAAM,IAAI,CAAC,SAAS;AACnB,YAAM,OAAO,KAAK,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ;AACnD,YAAM,WAAW,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,QAAQ;AACtD,YAAM,cAAc,gBAAgB,KAAK,MAAM;AAE/C,aACE,gBAAAA,MAAC,SAAsB,WAAU,8BAE/B;AAAA,wBAAAA,MAAC,SAAI,WAAU,kEACb;AAAA,0BAAAD,KAAC,UAAK,WAAU,mDACb,uBACH;AAAA,UACA,gBAAAC,MAAC,SAAI,WAAU,6BACZ;AAAA,iBAAK,SAAS,KACb,gBAAAD,KAAC,UAAK,WAAU,4EACb,eAAK,QACR;AAAA,YAED,SAAS,SAAS,KACjB,gBAAAA,KAAC,UAAK,WAAU,qFACb,mBAAS,QACZ;AAAA,aAEJ;AAAA,WACF;AAAA,QAGC,CAAC,GAAG,MAAM,GAAG,QAAQ,EAAE,IAAI,CAAC,WAAW;AACtC,gBAAM,eAAe,OAAO,SAAS,CAAC;AACtC,cAAI,CAAC,aAAc,QAAO;AAC1B,gBAAM,aAAa,OAAO;AAE1B,iBACE,gBAAAC;AAAA,YAAC;AAAA;AAAA,cAEC,SAAS,MAAM;AAEb,sBAAM,OAAO,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AACjD,uBAAO,SAAS,OAAO,OAAO,cAAc,OAAO;AAAA,cACrD;AAAA,cACA,WAAW,+FACT,aAAa,eAAe,EAC9B;AAAA,cAEA;AAAA,gCAAAA,MAAC,SAAI,WAAU,0CACb;AAAA,kCAAAA,MAAC,SAAI,WAAU,2BACb;AAAA,oCAAAD;AAAA,sBAAC;AAAA;AAAA,wBACC,WAAU;AAAA,wBACV,OAAO,EAAE,iBAAiB,aAAa,OAAO,MAAM;AAAA,wBAEnD,uBAAa,OAAO,KAAK,CAAC,GAAG,YAAY;AAAA;AAAA,oBAC5C;AAAA,oBACA,gBAAAA,KAAC,UAAK,WAAU,wCACb,uBAAa,OAAO,MACvB;AAAA,qBACF;AAAA,kBACA,gBAAAA,KAAC,UAAK,WAAU,gCACb,UAAAE,SAAQ,aAAa,SAAS,GACjC;AAAA,mBACF;AAAA,gBACA,gBAAAF,KAAC,OAAE,WAAU,8CACV,uBAAa,MAChB;AAAA,gBACC,OAAO,SAAS,SAAS,KACxB,gBAAAC,MAAC,UAAK,WAAU,qCACb;AAAA,yBAAO,SAAS,SAAS;AAAA,kBAAE;AAAA,kBAAE,OAAO,SAAS,SAAS,MAAM,IAAI,UAAU;AAAA,mBAC7E;AAAA;AAAA;AAAA,YAhCG,OAAO;AAAA,UAkCd;AAAA,QAEJ,CAAC;AAAA,WAhEO,KAAK,MAiEf;AAAA,IAEJ,CAAC;AAAA,KACH;AAEJ;AAIA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,eAAe,OAAO,SAAS,CAAC;AACtC,MAAI,CAAC,aAAc,QAAO;AAE1B,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,SAAS;AAAA,MACT,WAAW;AAAA,UACP,WAAW,eAAe,EAAE;AAAA,MAEhC;AAAA,wBAAAA,MAAC,SAAI,WAAU,0CACb;AAAA,0BAAAA,MAAC,SAAI,WAAU,2BACb;AAAA,4BAAAD;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO,EAAE,iBAAiB,aAAa,OAAO,MAAM;AAAA,gBAEnD,uBAAa,OAAO,KAAK,CAAC,GAAG,YAAY;AAAA;AAAA,YAC5C;AAAA,YACA,gBAAAA,KAAC,UAAK,WAAU,wCACb,uBAAa,OAAO,MACvB;AAAA,aACF;AAAA,UACA,gBAAAC,MAAC,SAAI,WAAU,2BACb;AAAA,4BAAAD,KAAC,UAAK,WAAU,gCACb,UAAAE,SAAQ,aAAa,SAAS,GACjC;AAAA,YACA,gBAAAF;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,CAAC,MAAM;AACd,oBAAE,gBAAgB;AAClB,4BAAU;AAAA,gBACZ;AAAA,gBACA,WAAU;AAAA,gBAET,qBACC,gBAAAA,KAACO,QAAA,EAAM,WAAU,4BAA2B,IAE5C,gBAAAP,KAACQ,QAAA,EAAM,WAAU,4BAA2B;AAAA;AAAA,YAEhD;AAAA,aACF;AAAA,WACF;AAAA,QACC,OAAO,eACN,gBAAAR,KAAC,UAAK,WAAU,iGACb,iBAAO,aACV;AAAA,QAEF,gBAAAA,KAAC,OAAE,WAAU,8CACV,uBAAa,MAChB;AAAA,QACC,OAAO,SAAS,SAAS,KACxB,gBAAAC,MAAC,UAAK,WAAU,qCACb;AAAA,iBAAO,SAAS,SAAS;AAAA,UAAE;AAAA,UAAE,OAAO,SAAS,SAAS,MAAM,IAAI,UAAU;AAAA,WAC7E;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":["useState","useCallback","useRef","useEffect","useState","useEffect","useCallback","jsx","useState","useEffect","useCallback","useEffect","useRef","useState","useCallback","useState","useRef","useEffect","jsx","jsxs","jsx","jsxs","useRef","useState","useCallback","useEffect","useState","jsx","jsxs","useState","jsx","jsxs","useRef","useState","useCallback","useEffect","jsx","jsxs","useState","useEffect","X","Check","Undo2","MessageSquare","Fragment","jsx","jsxs","timeAgo","useState","useEffect","MessageSquare","X","Undo2","Check"]}
1
+ {"version":3,"sources":["../src/context.tsx","../src/utils.ts","../src/debug.ts","../src/hooks/use-comments.ts","../src/hooks/use-comment-mode.ts","../src/components/comment-overlay.tsx","../src/components/comment-pin.tsx","../src/components/comment-thread.tsx","../src/icons.tsx","../src/components/comment-composer.tsx","../src/components/user-prompt.tsx","../src/components/comment-toggle.tsx","../src/components/comment-sidebar.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useState,\n useEffect,\n useCallback,\n useRef,\n type ReactNode,\n} from \"react\";\nimport type { ApostilThread, ApostilUser, ApostilStorage } from \"./types\";\nimport { createRestAdapter } from \"./adapters/rest\";\nimport { generateId, loadUser, saveUser, getRandomColor } from \"./utils\";\nimport { debug } from \"./debug\";\n\n// Stable default adapter (created once, not per render)\nconst defaultAdapter = createRestAdapter(\"/api/apostil\");\n\ntype ApostilContextValue = {\n threads: ApostilThread[];\n user: ApostilUser | null;\n commentMode: boolean;\n activeThreadId: string | null;\n sidebarOpen: boolean;\n setCommentMode: (on: boolean) => void;\n setActiveThreadId: (id: string | null) => void;\n setSidebarOpen: (open: boolean) => void;\n addThread: (pinX: number, pinY: number, body: string, targetId?: string, targetLabel?: string) => void;\n addReply: (threadId: string, body: string) => void;\n resolveThread: (threadId: string) => void;\n deleteThread: (threadId: string) => void;\n setUser: (name: string) => void;\n unresolvedCount: number;\n};\n\nconst ApostilContext = createContext<ApostilContextValue | null>(null);\n\nexport function ApostilProvider({\n pageId,\n storage,\n children,\n}: {\n pageId: string;\n storage?: ApostilStorage;\n children: ReactNode;\n}) {\n const adapter = storage ?? defaultAdapter;\n const [threads, setThreads] = useState<ApostilThread[]>([]);\n const [user, setUserState] = useState<ApostilUser | null>(null);\n const [commentMode, setCommentMode] = useState(false);\n const [activeThreadId, setActiveThreadId] = useState<string | null>(null);\n const [sidebarOpen, setSidebarOpen] = useState(false);\n const [loaded, setLoaded] = useState(false);\n\n useEffect(() => {\n const saved = loadUser();\n if (saved) setUserState(saved);\n }, []);\n\n // Track current pageId to avoid saving stale data during transitions\n const pageIdRef = useRef(pageId);\n\n useEffect(() => {\n pageIdRef.current = pageId;\n setLoaded(false);\n debug.log(\"loading threads for pageId:\", pageId);\n adapter.load(pageId).then((t) => {\n // Only apply if pageId hasn't changed during the fetch\n if (pageIdRef.current === pageId) {\n debug.log(\"loaded\", t.length, \"threads for\", pageId);\n setThreads(t);\n setLoaded(true);\n }\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pageId]);\n\n useEffect(() => {\n if (loaded && pageIdRef.current === pageId && threads.length > 0) {\n debug.log(\"saving\", threads.length, \"threads for pageId:\", pageId);\n adapter.save(pageId, threads);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [threads, pageId, loaded]);\n\n const setUser = useCallback((name: string) => {\n const u: ApostilUser = { id: generateId(), name, color: getRandomColor() };\n setUserState(u);\n saveUser(u);\n }, []);\n\n const addThread = useCallback(\n (pinX: number, pinY: number, body: string, targetId?: string, targetLabel?: string) => {\n if (!user) return;\n const threadId = generateId();\n const thread: ApostilThread = {\n id: threadId,\n pageId,\n pinX, pinY,\n targetId, targetLabel,\n resolved: false,\n createdAt: new Date().toISOString(),\n comments: [{\n id: generateId(), threadId, author: user, body,\n createdAt: new Date().toISOString(),\n }],\n };\n debug.log(\"new thread:\", { threadId, pinX, pinY, targetId, targetLabel, body });\n setThreads((prev) => [...prev, thread]);\n setActiveThreadId(threadId);\n setCommentMode(false);\n },\n [user, pageId]\n );\n\n const addReply = useCallback(\n (threadId: string, body: string) => {\n if (!user) return;\n setThreads((prev) =>\n prev.map((t) =>\n t.id === threadId\n ? { ...t, comments: [...t.comments, { id: generateId(), threadId, author: user, body, createdAt: new Date().toISOString() }] }\n : t\n )\n );\n },\n [user]\n );\n\n const resolveThread = useCallback((threadId: string) => {\n setThreads((prev) => prev.map((t) => t.id === threadId ? { ...t, resolved: !t.resolved } : t));\n setActiveThreadId(null);\n }, []);\n\n const deleteThread = useCallback((threadId: string) => {\n setThreads((prev) => prev.filter((t) => t.id !== threadId));\n setActiveThreadId(null);\n }, []);\n\n const unresolvedCount = threads.filter((t) => !t.resolved).length;\n\n return (\n <ApostilContext.Provider value={{\n threads, user, commentMode, activeThreadId, sidebarOpen,\n setCommentMode, setActiveThreadId, setSidebarOpen,\n addThread, addReply, resolveThread, deleteThread, setUser,\n unresolvedCount,\n }}>\n {children}\n </ApostilContext.Provider>\n );\n}\n\nexport function useApostil() {\n const ctx = useContext(ApostilContext);\n if (!ctx) throw new Error(\"useApostil must be used within ApostilProvider\");\n return ctx;\n}\n","let counter = 0;\n\nexport function generateId(): string {\n return `${Date.now()}-${++counter}-${Math.random().toString(36).slice(2, 7)}`;\n}\n\nconst USER_KEY = \"apostil-user\";\n\nexport function loadUser(): { id: string; name: string; color: string } | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(USER_KEY);\n return raw ? JSON.parse(raw) : null;\n } catch {\n return null;\n }\n}\n\nexport function saveUser(user: { id: string; name: string; color: string }) {\n if (typeof window === \"undefined\") return;\n localStorage.setItem(USER_KEY, JSON.stringify(user));\n}\n\nconst USER_COLORS = [\n \"#df461c\", \"#2563eb\", \"#16a34a\", \"#9333ea\", \"#ea580c\",\n \"#0891b2\", \"#c026d3\", \"#4f46e5\", \"#059669\", \"#dc2626\",\n];\n\nexport function getRandomColor(): string {\n return USER_COLORS[Math.floor(Math.random() * USER_COLORS.length)];\n}\n","const PREFIX = \"[apostil]\";\n\nfunction isDebug(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return localStorage.getItem(\"apostil-debug\") === \"true\";\n } catch {\n return false;\n }\n}\n\nexport const debug = {\n log(...args: unknown[]) {\n if (isDebug()) console.log(PREFIX, ...args);\n },\n warn(...args: unknown[]) {\n if (isDebug()) console.warn(PREFIX, ...args);\n },\n error(...args: unknown[]) {\n console.error(PREFIX, ...args);\n },\n /** Enable/disable debug logging */\n enable() {\n if (typeof window !== \"undefined\") {\n localStorage.setItem(\"apostil-debug\", \"true\");\n console.log(PREFIX, \"debug logging enabled — reload to take effect\");\n }\n },\n disable() {\n if (typeof window !== \"undefined\") {\n localStorage.removeItem(\"apostil-debug\");\n console.log(PREFIX, \"debug logging disabled\");\n }\n },\n};\n\n// Expose globally for easy toggling from console\nif (typeof window !== \"undefined\") {\n (window as unknown as Record<string, unknown>).__apostil_debug = debug;\n}\n","\"use client\";\n\nimport { useApostil } from \"../context\";\n\nexport function useComments() {\n const { threads, addThread, addReply, resolveThread, deleteThread, unresolvedCount } = useApostil();\n\n return {\n threads,\n openThreads: threads.filter((t) => !t.resolved),\n resolvedThreads: threads.filter((t) => t.resolved),\n addThread,\n addReply,\n resolveThread,\n deleteThread,\n unresolvedCount,\n };\n}\n","\"use client\";\n\nimport { useApostil } from \"../context\";\n\nexport function useCommentMode() {\n const { commentMode, setCommentMode, sidebarOpen, setSidebarOpen } = useApostil();\n\n return {\n commentMode,\n setCommentMode,\n toggleCommentMode: () => setCommentMode(!commentMode),\n sidebarOpen,\n setSidebarOpen,\n toggleSidebar: () => setSidebarOpen(!sidebarOpen),\n };\n}\n","\"use client\";\n\nimport { useState, useCallback, useRef, useEffect } from \"react\";\nimport { useApostil } from \"../context\";\nimport { debug } from \"../debug\";\nimport { CommentPin } from \"./comment-pin\";\nimport { ApostilThreadPopover } from \"./comment-thread\";\nimport { CommentComposer } from \"./comment-composer\";\nimport { UserPrompt } from \"./user-prompt\";\n\ntype PendingPin = {\n x: number;\n y: number;\n targetId?: string;\n targetLabel?: string;\n};\n\n// Find the highest z-index on the page (cached per toggle)\nlet cachedHighZ = 0;\nlet cacheTimestamp = 0;\nfunction getHighestZIndex(): number {\n const now = Date.now();\n if (now - cacheTimestamp < 500) return cachedHighZ; // cache for 500ms\n let max = 0;\n const els = document.querySelectorAll(\"[style*='z-index'], [class*='z-']\");\n for (let i = 0; i < els.length; i++) {\n const z = parseInt(getComputedStyle(els[i]).zIndex, 10);\n if (!isNaN(z) && z > max) max = z;\n }\n cachedHighZ = Math.max(max, 100); // minimum 100\n cacheTimestamp = now;\n return cachedHighZ;\n}\n\n// Semantic elements that are meaningful containers\nconst SEMANTIC_TAGS = new Set([\n \"SECTION\", \"NAV\", \"ASIDE\", \"HEADER\", \"FOOTER\", \"MAIN\",\n \"ARTICLE\", \"FORM\", \"DIALOG\", \"DETAILS\",\n]);\n\n// Min size for a target, and max size ratio to viewport to avoid matching the whole page\nconst MIN_TARGET_SIZE = 50;\nconst MAX_VIEWPORT_RATIO = 0.85; // skip elements covering >85% of viewport in both dimensions\n\n/**\n * Check if a div is a \"visual panel\" — a scrollable area, flex/grid child with defined bounds,\n * or a container with border/background that forms a distinct visual region.\n */\nfunction isVisualPanel(el: HTMLElement): boolean {\n const style = getComputedStyle(el);\n\n // Scrollable container\n if (/auto|scroll/.test(style.overflow + style.overflowY + style.overflowX)) return true;\n\n // Has border or distinct background (likely a card/panel)\n if (style.borderWidth && style.borderWidth !== \"0px\" && style.borderStyle !== \"none\") return true;\n if (style.borderRadius && style.borderRadius !== \"0px\") return true;\n if (style.backgroundColor && style.backgroundColor !== \"rgba(0, 0, 0, 0)\" && style.backgroundColor !== \"transparent\") return true;\n if (style.boxShadow && style.boxShadow !== \"none\") return true;\n\n return false;\n}\n\n/**\n * Try to get a readable label from an element's content.\n * Looks for headings, strong text, or first significant text node nearby.\n */\nfunction inferLabel(el: HTMLElement): string | null {\n // Check for a heading child\n const heading = el.querySelector(\"h1, h2, h3, h4, h5, h6, [class*='title'], [class*='heading']\");\n if (heading) {\n const text = heading.textContent?.trim();\n if (text && text.length <= 40) return text;\n }\n\n // Check for a label-like first child text\n const firstText = el.querySelector(\"span, p, label, strong\");\n if (firstText) {\n const text = firstText.textContent?.trim();\n if (text && text.length <= 30) return text;\n }\n\n return null;\n}\n\n/**\n * Build a stable CSS selector path for an element.\n */\nfunction getElementId(el: HTMLElement): string {\n const manual = el.getAttribute(\"data-comment-target\");\n if (manual) return manual;\n\n if (el.id) return `#${el.id}`;\n\n const label = el.getAttribute(\"aria-label\");\n if (label) return `${el.tagName.toLowerCase()}[aria-label=\"${label}\"]`;\n\n // nth-child path from nearest identifiable ancestor\n const parts: string[] = [];\n let cur: HTMLElement | null = el;\n for (let depth = 0; cur && depth < 5; depth++) {\n if (cur.id) {\n parts.unshift(`#${cur.id}`);\n break;\n }\n const p: HTMLElement | null = cur.parentElement;\n if (p) {\n const siblings = Array.from(p.children);\n const idx = siblings.indexOf(cur);\n parts.unshift(`${cur.tagName.toLowerCase()}:nth-child(${idx + 1})`);\n } else {\n parts.unshift(cur.tagName.toLowerCase());\n }\n cur = p;\n }\n return parts.join(\" > \");\n}\n\n/**\n * Derive a human-readable label for an element.\n */\nfunction getElementLabel(el: HTMLElement): string {\n const manual = el.getAttribute(\"data-comment-label\");\n if (manual) return manual;\n\n const ariaLabel = el.getAttribute(\"aria-label\");\n if (ariaLabel) return ariaLabel;\n\n // Try to infer from content\n const inferred = inferLabel(el);\n if (inferred) return inferred;\n\n const role = el.getAttribute(\"role\");\n\n if (el.id) {\n const pretty = el.id\n .replace(/[-_]/g, \" \")\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n return role ? `${pretty} (${role})` : pretty;\n }\n\n const tag = el.tagName.toLowerCase();\n if (SEMANTIC_TAGS.has(el.tagName)) return role ? `${tag} (${role})` : tag;\n if (role) return role;\n\n return tag;\n}\n\n/**\n * Walk up from the clicked element and find the best container to anchor the comment to.\n * Scores candidates by specificity — prefers the innermost meaningful panel.\n */\nfunction findCommentTarget(el: HTMLElement, boundary: HTMLElement | null) {\n let current: HTMLElement | null = el;\n const candidates: { el: HTMLElement; score: number; depth: number }[] = [];\n let depth = 0;\n\n while (current && current !== boundary && current !== document.body) {\n const rect = current.getBoundingClientRect();\n const isBigEnough = rect.width >= MIN_TARGET_SIZE && rect.height >= MIN_TARGET_SIZE;\n const isTooBig =\n rect.width > window.innerWidth * MAX_VIEWPORT_RATIO &&\n rect.height > window.innerHeight * MAX_VIEWPORT_RATIO;\n\n if (isBigEnough && !isTooBig) {\n let score = 0;\n\n // Manual target — highest priority\n if (current.getAttribute(\"data-comment-target\")) score = 100;\n // id\n else if (current.id) score = 80;\n // aria-label\n else if (current.getAttribute(\"aria-label\")) score = 70;\n // role attribute\n else if (current.getAttribute(\"role\")) score = 60;\n // semantic HTML tag\n else if (SEMANTIC_TAGS.has(current.tagName)) score = 50;\n // Visual panel (scrollable, bordered, shadowed, rounded)\n else if (isVisualPanel(current)) score = 40;\n\n if (score > 0) {\n candidates.push({ el: current, score, depth });\n }\n }\n\n current = current.parentElement;\n depth++;\n }\n\n if (candidates.length === 0) return null;\n\n // Prefer innermost among equally-scored candidates.\n // Among different scores: higher score wins, but add a bonus for being closer to the click.\n candidates.sort((a, b) => {\n const scoreA = a.score + Math.max(0, 10 - a.depth);\n const scoreB = b.score + Math.max(0, 10 - b.depth);\n return scoreB - scoreA;\n });\n\n const best = candidates[0];\n const targetId = getElementId(best.el);\n const targetLabel = getElementLabel(best.el);\n\n debug.log(\" candidates:\", candidates.map((c) => ({\n tag: c.el.tagName.toLowerCase(),\n id: c.el.id || undefined,\n class: c.el.className?.toString().slice(0, 60) || undefined,\n score: c.score,\n depth: c.depth,\n label: getElementLabel(c.el),\n })));\n\n return {\n targetId,\n targetLabel,\n element: best.el,\n };\n}\n\nexport function CommentOverlay() {\n const { threads, commentMode, setCommentMode, user, addThread, setActiveThreadId } =\n useApostil();\n const overlayRef = useRef<HTMLDivElement>(null);\n const [pendingPin, setPendingPin] = useState<PendingPin | null>(null);\n const [pendingPixel, setPendingPixel] = useState<{ left: number; top: number } | null>(null);\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!commentMode || !overlayRef.current) return;\n\n const overlayRect = overlayRef.current.getBoundingClientRect();\n\n // Temporarily hide overlay so elementFromPoint hits the actual content\n const overlay = overlayRef.current;\n overlay.style.pointerEvents = \"none\";\n const elementBelow = document.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;\n overlay.style.pointerEvents = \"\";\n\n debug.log(\" click at\", { clientX: e.clientX, clientY: e.clientY });\n debug.log(\" element below overlay:\", elementBelow);\n\n // Log the DOM path for debugging\n if (elementBelow) {\n const path: string[] = [];\n let walk: HTMLElement | null = elementBelow;\n while (walk && walk !== document.body) {\n const attrs: string[] = [walk.tagName.toLowerCase()];\n if (walk.id) attrs.push(`#${walk.id}`);\n if (walk.getAttribute(\"data-comment-target\")) attrs.push(`[data-comment-target=\"${walk.getAttribute(\"data-comment-target\")}\"]`);\n if (walk.getAttribute(\"aria-label\")) attrs.push(`[aria-label=\"${walk.getAttribute(\"aria-label\")}\"]`);\n if (walk.getAttribute(\"role\")) attrs.push(`[role=\"${walk.getAttribute(\"role\")}\"]`);\n if (SEMANTIC_TAGS.has(walk.tagName)) attrs.push(\"(semantic)\");\n path.push(attrs.join(\"\"));\n walk = walk.parentElement;\n }\n debug.log(\" DOM path:\", path.join(\" → \"));\n }\n\n const target = elementBelow ? findCommentTarget(elementBelow, overlayRef.current) : null;\n\n if (target) {\n const targetRect = target.element.getBoundingClientRect();\n const x = ((e.clientX - targetRect.left) / targetRect.width) * 100;\n const y = ((e.clientY - targetRect.top) / targetRect.height) * 100;\n debug.log(\" ✅ target found:\", {\n targetId: target.targetId,\n targetLabel: target.targetLabel,\n element: target.element,\n relativePos: { x: x.toFixed(1), y: y.toFixed(1) },\n targetRect: { w: targetRect.width, h: targetRect.height },\n });\n setPendingPin({ x, y, targetId: target.targetId, targetLabel: target.targetLabel });\n } else {\n const x = ((e.clientX - overlayRect.left) / overlayRect.width) * 100;\n const y = ((e.clientY - overlayRect.top) / overlayRect.height) * 100;\n debug.log(\" ⚠️ no target found, using overlay-relative position:\", {\n x: x.toFixed(1),\n y: y.toFixed(1),\n });\n setPendingPin({ x, y });\n }\n\n setPendingPixel({\n left: e.clientX - overlayRect.left,\n top: e.clientY - overlayRect.top,\n });\n setActiveThreadId(null);\n },\n [commentMode, setActiveThreadId]\n );\n\n const handleNewComment = useCallback(\n (body: string) => {\n if (!pendingPin) return;\n debug.log(\" saving thread:\", {\n pinX: pendingPin.x.toFixed(1),\n pinY: pendingPin.y.toFixed(1),\n targetId: pendingPin.targetId ?? \"(none)\",\n targetLabel: pendingPin.targetLabel ?? \"(none)\",\n body,\n });\n addThread(pendingPin.x, pendingPin.y, body, pendingPin.targetId, pendingPin.targetLabel);\n setPendingPin(null);\n setPendingPixel(null);\n },\n [pendingPin, addThread]\n );\n\n const cancelPending = useCallback(() => {\n setPendingPin(null);\n setPendingPixel(null);\n setCommentMode(false);\n }, [setCommentMode]);\n\n // Keyboard shortcuts\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n const tag = (e.target as HTMLElement)?.tagName;\n\n // Escape always works — even when typing\n if (e.key === \"Escape\") {\n if (pendingPin) {\n setPendingPin(null);\n setPendingPixel(null);\n setCommentMode(false);\n } else if (commentMode) {\n setCommentMode(false);\n }\n return;\n }\n\n // Other shortcuts only when not typing\n if (tag === \"INPUT\" || tag === \"TEXTAREA\") return;\n\n if (e.key === \"c\" || e.key === \"C\") {\n if (!commentMode) {\n debug.log(\" comment mode ON\");\n setActiveThreadId(null);\n setCommentMode(true);\n } else {\n debug.log(\" comment mode OFF\");\n setCommentMode(false);\n }\n }\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [commentMode, pendingPin, setCommentMode, setActiveThreadId]);\n\n // Sort: unresolved first, then resolved\n const sortedThreads = [...threads].sort((a, b) => {\n if (a.resolved !== b.resolved) return a.resolved ? 1 : -1;\n return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();\n });\n\n return (\n <>\n <UserPrompt />\n\n {/* Overlay layer */}\n <div\n ref={overlayRef}\n className={`fixed inset-0 ${\n commentMode\n ? \"cursor-crosshair pointer-events-auto\"\n : \"pointer-events-none\"\n }`}\n style={{ zIndex: commentMode ? getHighestZIndex() + 10 : 55 }}\n onMouseDown={handleClick}\n >\n {/* Existing pins */}\n {sortedThreads.map((thread, i) => (\n <div key={thread.id} className=\"pointer-events-auto\">\n <CommentPin thread={thread} index={i} overlayRef={overlayRef} />\n <ApostilThreadPopover thread={thread} overlayRef={overlayRef} />\n </div>\n ))}\n\n {/* Pending pin */}\n {pendingPin && pendingPixel && user && (\n <div\n className=\"absolute z-[70] pointer-events-auto\"\n style={{\n left: pendingPixel.left,\n top: pendingPixel.top,\n }}\n onMouseDown={(e) => e.stopPropagation()}\n onClick={(e) => e.stopPropagation()}\n >\n <div\n className=\"absolute -translate-x-1/2 -translate-y-1/2 w-7 h-7 rounded-full\n flex items-center justify-center text-white text-xs font-semibold\n shadow-lg ring-2 ring-white ring-offset-2 animate-bounce\"\n style={{ backgroundColor: user.color }}\n >\n +\n </div>\n <div className=\"absolute ml-5 -mt-3 w-72\">\n <div className=\"bg-white rounded-xl shadow-2xl border border-neutral-200 p-3\">\n <div className=\"flex items-center gap-2 mb-2\">\n <p className=\"text-xs text-neutral-500\">New comment</p>\n {pendingPin.targetLabel && (\n <span className=\"text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium\">\n {pendingPin.targetLabel}\n </span>\n )}\n </div>\n <CommentComposer\n onSubmit={handleNewComment}\n placeholder=\"What's on your mind?\"\n autoFocus\n />\n <button\n onClick={cancelPending}\n className=\"mt-2 text-xs text-neutral-400 hover:text-neutral-600 transition-colors\"\n >\n Cancel\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Comment mode hint */}\n {commentMode && !pendingPin && (\n <div className=\"absolute bottom-6 left-1/2 -translate-x-1/2 z-[60] pointer-events-none\">\n <div className=\"bg-neutral-900/80 text-white text-sm px-4 py-2 rounded-full backdrop-blur-sm\">\n Click anywhere to add a comment\n </div>\n </div>\n )}\n </>\n );\n}\n","\"use client\";\n\nimport { useState, useEffect, useCallback, type RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useApostil } from \"../context\";\nimport type { ApostilThread } from \"../types\";\n\n/**\n * Find the target element using the stored targetId.\n * targetId can be a CSS selector or a data-comment-target value.\n */\nfunction findTargetElement(targetId: string): HTMLElement | null {\n // First try as a CSS selector\n try {\n const el = document.querySelector(targetId);\n if (el instanceof HTMLElement) return el;\n } catch {\n // Invalid selector — fall through\n }\n\n // Then try as a data-comment-target value\n try {\n const el = document.querySelector(`[data-comment-target=\"${CSS.escape(targetId)}\"]`);\n if (el instanceof HTMLElement) return el;\n } catch {\n // noop\n }\n\n return null;\n}\n\n/**\n * Resolves pixel position for a thread pin relative to the overlay.\n * Used only for NON-targeted pins (no targetId).\n */\nfunction resolveOverlayPosition(\n thread: ApostilThread,\n overlayEl: HTMLElement | null\n): { left: number; top: number } | null {\n if (!overlayEl) return null;\n const overlayRect = overlayEl.getBoundingClientRect();\n return {\n left: (thread.pinX / 100) * overlayRect.width,\n top: (thread.pinY / 100) * overlayRect.height,\n };\n}\n\n/**\n * Resolves pixel position relative to the overlay, for targeted pins.\n * Used by the thread popover which always renders in the overlay.\n */\nfunction resolvePosition(\n thread: ApostilThread,\n overlayEl: HTMLElement | null\n): { left: number; top: number } | null {\n if (!overlayEl) return null;\n const overlayRect = overlayEl.getBoundingClientRect();\n\n if (thread.targetId) {\n const target = findTargetElement(thread.targetId);\n if (target) {\n const targetRect = target.getBoundingClientRect();\n return {\n left: targetRect.left - overlayRect.left + (thread.pinX / 100) * targetRect.width,\n top: targetRect.top - overlayRect.top + (thread.pinY / 100) * targetRect.height,\n };\n }\n return null;\n }\n\n return {\n left: (thread.pinX / 100) * overlayRect.width,\n top: (thread.pinY / 100) * overlayRect.height,\n };\n}\n\n// ─── Pin button (shared rendering) ────────────────────────────────\n\nfunction PinButton({\n thread,\n index,\n isActive,\n onClick,\n}: {\n thread: ApostilThread;\n index: number;\n isActive: boolean;\n onClick: (e: React.MouseEvent) => void;\n}) {\n const authorColor = thread.comments[0]?.author.color ?? \"#df461c\";\n\n return (\n <button onClick={onClick} className=\"group\" style={{ position: \"relative\" }}>\n <div\n className={`\n flex items-center justify-center\n w-7 h-7 rounded-full text-white text-xs font-semibold\n shadow-lg cursor-pointer\n transition-all duration-200\n ${isActive ? \"scale-125 ring-2 ring-white ring-offset-2\" : \"hover:scale-110\"}\n ${thread.resolved ? \"opacity-40\" : \"\"}\n `}\n style={{ backgroundColor: authorColor }}\n >\n {index + 1}\n </div>\n {!thread.resolved && !isActive && (\n <div\n className=\"absolute inset-0 rounded-full animate-ping opacity-20\"\n style={{ backgroundColor: authorColor }}\n />\n )}\n {thread.targetLabel && (\n <div className=\"absolute -bottom-6 left-1/2 -translate-x-1/2 whitespace-nowrap\n opacity-0 group-hover:opacity-100 transition-opacity\n text-[10px] bg-neutral-800 text-white px-1.5 py-0.5 rounded pointer-events-none\">\n {thread.targetLabel}\n </div>\n )}\n </button>\n );\n}\n\n// ─── Targeted pin (portals into the target element) ───────────────\n\nfunction TargetedPin({\n thread,\n index,\n}: {\n thread: ApostilThread;\n index: number;\n}) {\n const { activeThreadId, setActiveThreadId } = useApostil();\n const isActive = activeThreadId === thread.id;\n const [targetEl, setTargetEl] = useState<HTMLElement | null>(null);\n\n useEffect(() => {\n if (!thread.targetId) return;\n\n function tryFind() {\n const el = findTargetElement(thread.targetId!);\n if (el) {\n const pos = getComputedStyle(el).position;\n if (pos === \"static\") el.style.position = \"relative\";\n setTargetEl(el);\n } else {\n setTargetEl(null);\n }\n }\n\n tryFind();\n\n // Watch for DOM changes — target element may appear/disappear (popovers, dialogs)\n const observer = new MutationObserver(() => tryFind());\n observer.observe(document.body, { childList: true, subtree: true });\n\n return () => observer.disconnect();\n }, [thread.targetId]);\n\n if (!targetEl) return null;\n\n return createPortal(\n <div\n className=\"absolute pointer-events-auto\"\n style={{\n left: `${thread.pinX}%`,\n top: `${thread.pinY}%`,\n transform: \"translate(-50%, -50%)\",\n zIndex: 10,\n }}\n >\n <PinButton\n thread={thread}\n index={index}\n isActive={isActive}\n onClick={(e) => {\n e.stopPropagation();\n setActiveThreadId(isActive ? null : thread.id);\n }}\n />\n </div>,\n targetEl\n );\n}\n\n// ─── Overlay pin (non-targeted, positioned in the overlay) ────────\n\nfunction OverlayPin({\n thread,\n index,\n overlayRef,\n}: {\n thread: ApostilThread;\n index: number;\n overlayRef: RefObject<HTMLDivElement | null>;\n}) {\n const { activeThreadId, setActiveThreadId } = useApostil();\n const isActive = activeThreadId === thread.id;\n const [pos, setPos] = useState<{ left: number; top: number } | null>(null);\n\n const updatePos = useCallback(() => {\n setPos(resolveOverlayPosition(thread, overlayRef.current));\n }, [thread, overlayRef]);\n\n useEffect(() => {\n updatePos();\n window.addEventListener(\"resize\", updatePos);\n return () => window.removeEventListener(\"resize\", updatePos);\n }, [updatePos]);\n\n if (!pos) return null;\n\n return (\n <div\n className=\"absolute pointer-events-auto\"\n style={{\n left: pos.left,\n top: pos.top,\n transform: \"translate(-50%, -50%)\",\n zIndex: 60,\n }}\n >\n <PinButton\n thread={thread}\n index={index}\n isActive={isActive}\n onClick={(e) => {\n e.stopPropagation();\n setActiveThreadId(isActive ? null : thread.id);\n }}\n />\n </div>\n );\n}\n\n// ─── Public CommentPin (delegates to targeted or overlay) ─────────\n\nexport function CommentPin({\n thread,\n index,\n overlayRef,\n}: {\n thread: ApostilThread;\n index: number;\n overlayRef: RefObject<HTMLDivElement | null>;\n}) {\n if (thread.targetId) {\n return <TargetedPin key={`targeted-${thread.id}`} thread={thread} index={index} />;\n }\n return <OverlayPin key={`overlay-${thread.id}`} thread={thread} index={index} overlayRef={overlayRef} />;\n}\n\nexport { resolvePosition, findTargetElement };\n","\"use client\";\n\nimport { useEffect, useRef, useState, useCallback, type RefObject } from \"react\";\nimport { Check, Trash2, Undo2 } from \"../icons\";\nimport { useApostil } from \"../context\";\nimport { CommentComposer } from \"./comment-composer\";\nimport { resolvePosition } from \"./comment-pin\";\nimport type { ApostilThread as ApostilThreadType } from \"../types\";\n\nfunction timeAgo(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return \"just now\";\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.floor(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n\nexport function ApostilThreadPopover({\n thread,\n overlayRef,\n}: {\n thread: ApostilThreadType;\n overlayRef: RefObject<HTMLDivElement | null>;\n}) {\n const { activeThreadId, setActiveThreadId, addReply, resolveThread, deleteThread, user } =\n useApostil();\n const ref = useRef<HTMLDivElement>(null);\n const isOpen = activeThreadId === thread.id;\n\n const [pos, setPos] = useState<{ left: number; top: number } | null>(null);\n\n const updatePos = useCallback(() => {\n setPos(resolvePosition(thread, overlayRef.current));\n }, [thread, overlayRef]);\n\n useEffect(() => {\n if (!isOpen) return;\n updatePos();\n window.addEventListener(\"resize\", updatePos);\n // Track scroll on any container so popover follows the pin\n document.addEventListener(\"scroll\", updatePos, true);\n return () => {\n window.removeEventListener(\"resize\", updatePos);\n document.removeEventListener(\"scroll\", updatePos, true);\n };\n }, [isOpen, updatePos]);\n\n useEffect(() => {\n if (!isOpen) return;\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) {\n setActiveThreadId(null);\n }\n };\n const timer = setTimeout(() => document.addEventListener(\"mousedown\", handler), 0);\n return () => {\n clearTimeout(timer);\n document.removeEventListener(\"mousedown\", handler);\n };\n }, [isOpen, setActiveThreadId]);\n\n if (!isOpen || !pos) return null;\n\n return (\n <div\n ref={ref}\n className=\"absolute z-[70] ml-5 -mt-3\"\n style={{ left: pos.left, top: pos.top }}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"w-80 bg-white rounded-xl shadow-2xl border border-neutral-200 overflow-hidden\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-2.5 border-b border-neutral-100 bg-neutral-50\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-medium text-neutral-500\">\n {thread.comments.length} {thread.comments.length === 1 ? \"comment\" : \"comments\"}\n </span>\n {thread.targetLabel && (\n <span className=\"text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium\">\n {thread.targetLabel}\n </span>\n )}\n {thread.resolved && (\n <span className=\"text-[10px] text-emerald-600 font-medium\">Resolved</span>\n )}\n </div>\n <div className=\"flex gap-1\">\n <button\n onClick={() => resolveThread(thread.id)}\n className=\"p-1 rounded hover:bg-neutral-200 transition-colors\"\n title={thread.resolved ? \"Reopen\" : \"Resolve\"}\n >\n {thread.resolved ? (\n <Undo2 className=\"w-3.5 h-3.5 text-neutral-500\" />\n ) : (\n <Check className=\"w-3.5 h-3.5 text-emerald-600\" />\n )}\n </button>\n <button\n onClick={() => deleteThread(thread.id)}\n className=\"p-1 rounded hover:bg-red-50 transition-colors\"\n title=\"Delete thread\"\n >\n <Trash2 className=\"w-3.5 h-3.5 text-neutral-400 hover:text-red-500\" />\n </button>\n </div>\n </div>\n\n {/* Comments */}\n <div className=\"max-h-64 overflow-y-auto\">\n {thread.comments.map((comment) => (\n <div key={comment.id} className=\"px-4 py-3 border-b border-neutral-50 last:border-0\">\n <div className=\"flex items-center gap-2 mb-1\">\n <div\n className=\"w-5 h-5 rounded-full flex items-center justify-center text-white text-[10px] font-semibold shrink-0\"\n style={{ backgroundColor: comment.author.color }}\n >\n {comment.author.name[0]?.toUpperCase()}\n </div>\n <span className=\"text-xs font-medium text-neutral-800\">\n {comment.author.name}\n </span>\n <span className=\"text-[10px] text-neutral-400 ml-auto\">\n {timeAgo(comment.createdAt)}\n </span>\n </div>\n <p className=\"text-sm text-neutral-700 leading-relaxed pl-7\">\n {comment.body}\n </p>\n </div>\n ))}\n </div>\n\n {/* Reply */}\n {user && !thread.resolved && (\n <div className=\"px-3 py-2.5 border-t border-neutral-100 bg-neutral-50/50\">\n <CommentComposer\n onSubmit={(body) => addReply(thread.id, body)}\n placeholder=\"Reply...\"\n autoFocus\n />\n </div>\n )}\n </div>\n </div>\n );\n}\n","import React from \"react\";\n\ntype IconProps = { className?: string };\n\nexport function Send({ className = \"w-4 h-4\" }: IconProps) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"m22 2-7 20-4-9-9-4Z\" />\n <path d=\"M22 2 11 13\" />\n </svg>\n );\n}\n\nexport function MessageSquare({ className = \"w-4 h-4\" }: IconProps) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n );\n}\n\nexport function List({ className = \"w-4 h-4\" }: IconProps) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"8\" x2=\"21\" y1=\"6\" y2=\"6\" />\n <line x1=\"8\" x2=\"21\" y1=\"12\" y2=\"12\" />\n <line x1=\"8\" x2=\"21\" y1=\"18\" y2=\"18\" />\n <line x1=\"3\" x2=\"3.01\" y1=\"6\" y2=\"6\" />\n <line x1=\"3\" x2=\"3.01\" y1=\"12\" y2=\"12\" />\n <line x1=\"3\" x2=\"3.01\" y1=\"18\" y2=\"18\" />\n </svg>\n );\n}\n\nexport function X({ className = \"w-4 h-4\" }: IconProps) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n );\n}\n\nexport function Check({ className = \"w-4 h-4\" }: IconProps) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n );\n}\n\nexport function Undo2({ className = \"w-4 h-4\" }: IconProps) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M9 14 4 9l5-5\" />\n <path d=\"M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11\" />\n </svg>\n );\n}\n\nexport function Globe({ className = \"w-4 h-4\" }: IconProps) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\" />\n <path d=\"M2 12h20\" />\n </svg>\n );\n}\n\nexport function FileText({ className = \"w-4 h-4\" }: IconProps) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z\" />\n <path d=\"M14 2v4a2 2 0 0 0 2 2h4\" />\n <path d=\"M10 9H8\" />\n <path d=\"M16 13H8\" />\n <path d=\"M16 17H8\" />\n </svg>\n );\n}\n\nexport function Trash2({ className = \"w-4 h-4\" }: IconProps) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M3 6h18\" />\n <path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\" />\n <path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\" />\n <line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\" />\n <line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\" />\n </svg>\n );\n}\n","\"use client\";\n\nimport { useState, useRef, useEffect } from \"react\";\nimport { Send } from \"../icons\";\n\nexport function CommentComposer({\n onSubmit,\n placeholder = \"Add a comment...\",\n autoFocus = false,\n}: {\n onSubmit: (body: string) => void;\n placeholder?: string;\n autoFocus?: boolean;\n}) {\n const [value, setValue] = useState(\"\");\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n useEffect(() => {\n if (autoFocus) {\n inputRef.current?.focus();\n }\n }, [autoFocus]);\n\n const handleSubmit = () => {\n const trimmed = value.trim();\n if (!trimmed) return;\n onSubmit(trimmed);\n setValue(\"\");\n };\n\n return (\n <div className=\"flex gap-2 items-end\">\n <textarea\n ref={inputRef}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n }}\n placeholder={placeholder}\n rows={1}\n className=\"flex-1 resize-none rounded-lg border border-neutral-200 bg-white px-3 py-2 text-sm\n placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-neutral-300\n min-h-[36px] max-h-[120px]\"\n />\n <button\n onClick={handleSubmit}\n disabled={!value.trim()}\n className=\"flex items-center justify-center w-8 h-8 rounded-lg\n bg-neutral-900 text-white disabled:opacity-30\n hover:bg-neutral-700 transition-colors shrink-0\"\n >\n <Send className=\"w-3.5 h-3.5\" />\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport { useState } from \"react\";\nimport { useApostil } from \"../context\";\n\nexport function UserPrompt() {\n const { user, setUser, commentMode } = useApostil();\n const [name, setName] = useState(\"\");\n\n // Only show when comment mode is activated and no user is set\n if (user || !commentMode) return null;\n\n const handleSubmit = () => {\n const trimmed = name.trim();\n if (!trimmed) return;\n setUser(trimmed);\n };\n\n return (\n <div className=\"absolute inset-0 z-[80] flex items-center justify-center bg-black/20 backdrop-blur-[2px]\">\n <div className=\"bg-white rounded-xl shadow-2xl border border-neutral-200 p-6 w-80\">\n <h3 className=\"text-sm font-semibold text-neutral-900 mb-1\">\n What&apos;s your name?\n </h3>\n <p className=\"text-xs text-neutral-500 mb-4\">\n This will be shown with your comments.\n </p>\n <input\n value={name}\n onChange={(e) => setName(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") handleSubmit();\n }}\n placeholder=\"Enter your name\"\n autoFocus\n className=\"w-full rounded-lg border border-neutral-200 px-3 py-2 text-sm\n placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-neutral-300 mb-3\"\n />\n <button\n onClick={handleSubmit}\n disabled={!name.trim()}\n className=\"w-full py-2 rounded-lg bg-neutral-900 text-white text-sm font-medium\n disabled:opacity-30 hover:bg-neutral-700 transition-colors\"\n >\n Continue\n </button>\n </div>\n </div>\n );\n}\n","\"use client\";\n\nimport { MessageSquare, List, X } from \"../icons\";\nimport { useApostil } from \"../context\";\n\nexport function CommentToggle() {\n const {\n commentMode,\n setCommentMode,\n sidebarOpen,\n setSidebarOpen,\n unresolvedCount,\n setActiveThreadId,\n } = useApostil();\n\n return (\n <div className=\"absolute bottom-5 right-5 z-[65] flex flex-col gap-2 items-end\">\n {/* Sidebar toggle */}\n <button\n onClick={() => setSidebarOpen(!sidebarOpen)}\n className={`flex items-center justify-center w-10 h-10 rounded-full shadow-lg\n transition-all duration-200 hover:scale-105\n ${sidebarOpen\n ? \"bg-neutral-900 text-white\"\n : \"bg-white text-neutral-600 hover:bg-neutral-50 border border-neutral-200\"\n }`}\n title=\"Toggle comment list\"\n >\n <List className=\"w-4 h-4\" />\n </button>\n\n {/* Comment mode toggle */}\n <button\n onClick={() => {\n if (commentMode) {\n setCommentMode(false);\n } else {\n setActiveThreadId(null);\n setCommentMode(true);\n }\n }}\n className={`relative flex items-center justify-center w-12 h-12 rounded-full shadow-lg\n transition-all duration-200 hover:scale-105\n ${commentMode\n ? \"bg-neutral-900 text-white ring-2 ring-neutral-400\"\n : \"bg-white text-neutral-700 hover:bg-neutral-50 border border-neutral-200\"\n }`}\n title={commentMode ? \"Exit comment mode\" : \"Add comment\"}\n >\n {commentMode ? (\n <X className=\"w-5 h-5\" />\n ) : (\n <MessageSquare className=\"w-5 h-5\" />\n )}\n {/* Unresolved badge */}\n {unresolvedCount > 0 && !commentMode && (\n <span className=\"absolute -top-1 -right-1 min-w-[18px] h-[18px] rounded-full\n bg-red-500 text-white text-[10px] font-semibold\n flex items-center justify-center px-1\">\n {unresolvedCount}\n </span>\n )}\n </button>\n </div>\n );\n}\n","\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { X, Check, Undo2, MessageSquare, Globe, FileText } from \"../icons\";\nimport { useApostil } from \"../context\";\nimport type { ApostilThread } from \"../types\";\n\nfunction timeAgo(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime();\n const mins = Math.floor(diff / 60000);\n if (mins < 1) return \"just now\";\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.floor(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n\nfunction pageIdToDisplay(pageId: string): string {\n return pageId.replace(/--/g, \"/\").replace(/-/g, \".\");\n}\n\ntype AllPagesData = { pageId: string; threads: ApostilThread[] }[];\n\nexport function CommentSidebar() {\n const {\n threads,\n sidebarOpen,\n setSidebarOpen,\n setActiveThreadId,\n resolveThread,\n } = useApostil();\n\n const [tab, setTab] = useState<\"page\" | \"all\">(\"page\");\n const [allPages, setAllPages] = useState<AllPagesData>([]);\n const [loadingAll, setLoadingAll] = useState(false);\n\n // Fetch all pages when \"All Pages\" tab is selected\n useEffect(() => {\n if (!sidebarOpen || tab !== \"all\") return;\n setLoadingAll(true);\n\n // Try CLI server first, then local API\n async function fetchAll() {\n // Fetch from the app's own API route (no pageId = returns all pages)\n try {\n const res = await fetch(\"/api/apostil\");\n if (res.ok) {\n const data = await res.json();\n setAllPages(data);\n }\n } catch {}\n setLoadingAll(false);\n }\n fetchAll();\n }, [sidebarOpen, tab]);\n\n if (!sidebarOpen) return null;\n\n const openThreads = threads.filter((t) => !t.resolved);\n const resolvedThreads = threads.filter((t) => t.resolved);\n\n return (\n <div className=\"absolute top-0 right-0 bottom-0 w-80 z-[75] bg-white border-l border-neutral-200 shadow-xl flex flex-col\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-neutral-100\">\n <div className=\"flex items-center gap-2\">\n <MessageSquare className=\"w-4 h-4 text-neutral-500\" />\n <span className=\"text-sm font-semibold text-neutral-900\">\n Comments\n </span>\n </div>\n <button\n onClick={() => setSidebarOpen(false)}\n className=\"p-1 rounded hover:bg-neutral-100 transition-colors\"\n >\n <X className=\"w-4 h-4 text-neutral-500\" />\n </button>\n </div>\n\n {/* Tabs */}\n <div className=\"flex border-b border-neutral-100\">\n <button\n onClick={() => setTab(\"page\")}\n className={`flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${\n tab === \"page\"\n ? \"text-neutral-900 border-b-2 border-neutral-900\"\n : \"text-neutral-400 hover:text-neutral-600\"\n }`}\n >\n <FileText className=\"w-3 h-3\" />\n This Page\n {openThreads.length > 0 && (\n <span className=\"text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full\">\n {openThreads.length}\n </span>\n )}\n </button>\n <button\n onClick={() => setTab(\"all\")}\n className={`flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors ${\n tab === \"all\"\n ? \"text-neutral-900 border-b-2 border-neutral-900\"\n : \"text-neutral-400 hover:text-neutral-600\"\n }`}\n >\n <Globe className=\"w-3 h-3\" />\n All Pages\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-y-auto\">\n {tab === \"page\" ? (\n <PageThreads\n threads={threads}\n openThreads={openThreads}\n resolvedThreads={resolvedThreads}\n onSelect={setActiveThreadId}\n onResolve={resolveThread}\n />\n ) : (\n <AllPagesView\n pages={allPages}\n loading={loadingAll}\n />\n )}\n </div>\n </div>\n );\n}\n\n// --- This Page tab ---\n\nfunction PageThreads({\n threads,\n openThreads,\n resolvedThreads,\n onSelect,\n onResolve,\n}: {\n threads: ApostilThread[];\n openThreads: ApostilThread[];\n resolvedThreads: ApostilThread[];\n onSelect: (id: string) => void;\n onResolve: (id: string) => void;\n}) {\n if (threads.length === 0) {\n return (\n <div className=\"p-6 text-center text-sm text-neutral-400\">\n No comments on this page.\n </div>\n );\n }\n\n return (\n <>\n {openThreads.length > 0 && (\n <div>\n <div className=\"px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider\">\n Open ({openThreads.length})\n </div>\n {openThreads.map((thread) => (\n <ThreadItem\n key={thread.id}\n thread={thread}\n onSelect={() => onSelect(thread.id)}\n onResolve={() => onResolve(thread.id)}\n />\n ))}\n </div>\n )}\n\n {resolvedThreads.length > 0 && (\n <div>\n <div className=\"px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider\">\n Resolved ({resolvedThreads.length})\n </div>\n {resolvedThreads.map((thread) => (\n <ThreadItem\n key={thread.id}\n thread={thread}\n onSelect={() => onSelect(thread.id)}\n onResolve={() => onResolve(thread.id)}\n resolved\n />\n ))}\n </div>\n )}\n </>\n );\n}\n\n// --- All Pages tab ---\n\nfunction AllPagesView({\n pages,\n loading,\n}: {\n pages: AllPagesData;\n loading: boolean;\n}) {\n if (loading) {\n return (\n <div className=\"p-6 text-center text-sm text-neutral-400\">\n Loading...\n </div>\n );\n }\n\n if (pages.length === 0) {\n return (\n <div className=\"p-6 text-center text-sm text-neutral-400\">\n No comments in this project yet.\n </div>\n );\n }\n\n const totalOpen = pages.reduce((s, p) => s + p.threads.filter((t) => !t.resolved).length, 0);\n\n return (\n <>\n {totalOpen > 0 && (\n <div className=\"px-4 py-2 text-[10px] font-semibold text-neutral-400 uppercase tracking-wider\">\n {totalOpen} open across {pages.length} pages\n </div>\n )}\n\n {pages.map((page) => {\n const open = page.threads.filter((t) => !t.resolved);\n const resolved = page.threads.filter((t) => t.resolved);\n const displayName = pageIdToDisplay(page.pageId);\n\n return (\n <div key={page.pageId} className=\"border-b border-neutral-50\">\n {/* Page header */}\n <div className=\"px-4 py-2.5 flex items-center justify-between bg-neutral-50/50\">\n <span className=\"text-xs font-semibold text-neutral-700 truncate\">\n {displayName}\n </span>\n <div className=\"flex items-center gap-1.5\">\n {open.length > 0 && (\n <span className=\"text-[10px] bg-red-50 text-red-600 px-1.5 py-px rounded-full font-medium\">\n {open.length}\n </span>\n )}\n {resolved.length > 0 && (\n <span className=\"text-[10px] bg-neutral-100 text-neutral-500 px-1.5 py-px rounded-full font-medium\">\n {resolved.length}\n </span>\n )}\n </div>\n </div>\n\n {/* Threads for this page */}\n {[...open, ...resolved].map((thread) => {\n const firstComment = thread.comments[0];\n if (!firstComment) return null;\n const isResolved = thread.resolved;\n\n return (\n <div\n key={thread.id}\n onClick={() => {\n // Navigate to the page with the comment hash\n const path = \"/\" + page.pageId.replace(/--/g, \"/\");\n window.location.href = path + \"#apostil-\" + thread.id;\n }}\n className={`px-4 py-2.5 border-b border-neutral-50 cursor-pointer hover:bg-neutral-50 transition-colors ${\n isResolved ? \"opacity-50\" : \"\"\n }`}\n >\n <div className=\"flex items-center justify-between mb-1\">\n <div className=\"flex items-center gap-2\">\n <div\n className=\"w-4 h-4 rounded-full flex items-center justify-center text-white text-[8px] font-semibold\"\n style={{ backgroundColor: firstComment.author.color }}\n >\n {firstComment.author.name[0]?.toUpperCase()}\n </div>\n <span className=\"text-xs font-medium text-neutral-700\">\n {firstComment.author.name}\n </span>\n </div>\n <span className=\"text-[10px] text-neutral-400\">\n {timeAgo(firstComment.createdAt)}\n </span>\n </div>\n <p className=\"text-xs text-neutral-600 line-clamp-2 pl-6\">\n {firstComment.body}\n </p>\n {thread.comments.length > 1 && (\n <span className=\"text-[10px] text-neutral-400 pl-6\">\n {thread.comments.length - 1} {thread.comments.length - 1 === 1 ? \"reply\" : \"replies\"}\n </span>\n )}\n </div>\n );\n })}\n </div>\n );\n })}\n </>\n );\n}\n\n// --- Shared thread item ---\n\nfunction ThreadItem({\n thread,\n onSelect,\n onResolve,\n resolved,\n}: {\n thread: { id: string; targetLabel?: string; comments: { author: { name: string; color: string }; body: string; createdAt: string }[] };\n onSelect: () => void;\n onResolve: () => void;\n resolved?: boolean;\n}) {\n const firstComment = thread.comments[0];\n if (!firstComment) return null;\n\n return (\n <div\n onClick={onSelect}\n className={`px-4 py-3 border-b border-neutral-50 cursor-pointer hover:bg-neutral-50 transition-colors\n ${resolved ? \"opacity-60\" : \"\"}`}\n >\n <div className=\"flex items-center justify-between mb-1\">\n <div className=\"flex items-center gap-2\">\n <div\n className=\"w-4 h-4 rounded-full flex items-center justify-center text-white text-[8px] font-semibold\"\n style={{ backgroundColor: firstComment.author.color }}\n >\n {firstComment.author.name[0]?.toUpperCase()}\n </div>\n <span className=\"text-xs font-medium text-neutral-700\">\n {firstComment.author.name}\n </span>\n </div>\n <div className=\"flex items-center gap-1\">\n <span className=\"text-[10px] text-neutral-400\">\n {timeAgo(firstComment.createdAt)}\n </span>\n <button\n onClick={(e) => {\n e.stopPropagation();\n onResolve();\n }}\n className=\"p-0.5 rounded hover:bg-neutral-200 transition-colors\"\n >\n {resolved ? (\n <Undo2 className=\"w-3 h-3 text-neutral-400\" />\n ) : (\n <Check className=\"w-3 h-3 text-emerald-600\" />\n )}\n </button>\n </div>\n </div>\n {thread.targetLabel && (\n <span className=\"inline-block text-[10px] bg-blue-50 text-blue-600 px-1.5 py-0.5 rounded font-medium ml-6 mb-1\">\n {thread.targetLabel}\n </span>\n )}\n <p className=\"text-xs text-neutral-600 line-clamp-2 pl-6\">\n {firstComment.body}\n </p>\n {thread.comments.length > 1 && (\n <span className=\"text-[10px] text-neutral-400 pl-6\">\n {thread.comments.length - 1} {thread.comments.length - 1 === 1 ? \"reply\" : \"replies\"}\n </span>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["useState","useCallback","useRef","useEffect","useState","useEffect","useCallback","jsx","useState","useEffect","useCallback","useEffect","useRef","useState","useCallback","jsx","jsxs","useState","useRef","useEffect","jsx","jsxs","useState","useRef","useEffect","jsx","jsxs","useRef","useState","useCallback","useEffect","useState","jsx","jsxs","useState","jsx","jsxs","useRef","useState","useCallback","useEffect","jsx","jsxs","useState","useEffect","Fragment","jsx","jsxs","timeAgo","useState","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostil",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Pin-and-comment feedback overlay for design prototypes and web apps",
5
5
  "license": "MIT",
6
6
  "author": "Batzorigco",
@@ -56,9 +56,7 @@
56
56
  "react": ">=18",
57
57
  "react-dom": ">=18"
58
58
  },
59
- "dependencies": {
60
- "lucide-react": ">=0.300.0"
61
- },
59
+ "dependencies": {},
62
60
  "devDependencies": {
63
61
  "@types/node": "^25.5.0",
64
62
  "@types/react": "^19",