@ourroadmaps/web-sdk 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -491,33 +491,51 @@ var PinManager = class {
491
491
  }
492
492
  document.body.appendChild(this.container);
493
493
  }
494
- addPin(pinNumber, x, y, isMine) {
494
+ addPin(pinNumber, x, y, isMine, pageUrl) {
495
495
  const pin = document.createElement("div");
496
496
  pin.className = isMine ? "review-pin" : "review-pin review-pin--other";
497
497
  pin.textContent = String(pinNumber);
498
498
  pin.style.left = `${x}%`;
499
499
  pin.style.top = `${y}%`;
500
500
  pin.dataset.pinNumber = String(pinNumber);
501
+ pin.dataset.pageUrl = pageUrl;
501
502
  this.container.appendChild(pin);
502
- this.pins.set(pinNumber, pin);
503
+ this.pins.set(pinNumber, { el: pin, pageUrl });
503
504
  }
504
505
  removePin(pinNumber) {
505
- const pin = this.pins.get(pinNumber);
506
- if (pin) {
507
- pin.remove();
506
+ const entry = this.pins.get(pinNumber);
507
+ if (entry) {
508
+ entry.el.remove();
508
509
  this.pins.delete(pinNumber);
509
510
  }
510
511
  }
511
512
  highlightPin(pinNumber) {
512
- for (const [num, el] of this.pins) {
513
+ for (const [num, { el }] of this.pins) {
513
514
  el.classList.toggle("review-pin--highlighted", num === pinNumber);
514
515
  }
515
516
  }
516
517
  clearHighlight() {
517
- for (const el of this.pins.values()) {
518
+ for (const { el } of this.pins.values()) {
518
519
  el.classList.remove("review-pin--highlighted");
519
520
  }
520
521
  }
522
+ filterByPage(currentPageId) {
523
+ for (const { el, pageUrl } of this.pins.values()) {
524
+ el.style.display = pageUrl === currentPageId ? "" : "none";
525
+ }
526
+ }
527
+ showAll() {
528
+ for (const { el } of this.pins.values()) {
529
+ el.style.display = "";
530
+ }
531
+ }
532
+ countForPage(pageId) {
533
+ let count = 0;
534
+ for (const { pageUrl } of this.pins.values()) {
535
+ if (pageUrl === pageId) count++;
536
+ }
537
+ return count;
538
+ }
521
539
  destroy() {
522
540
  this.container.remove();
523
541
  this.pins.clear();
@@ -609,6 +627,60 @@ var CommentCard = class {
609
627
  }
610
628
  };
611
629
 
630
+ // src/review/PageTracker.ts
631
+ function getCurrentPageId() {
632
+ return window.location.pathname + window.location.search + window.location.hash;
633
+ }
634
+ var PageNavigationListener = class {
635
+ constructor(onChange) {
636
+ this.onChange = onChange;
637
+ __publicField(this, "popstateHandler", null);
638
+ __publicField(this, "hashchangeHandler", null);
639
+ __publicField(this, "originalPushState", null);
640
+ __publicField(this, "originalReplaceState", null);
641
+ __publicField(this, "lastPageId");
642
+ this.lastPageId = getCurrentPageId();
643
+ }
644
+ start() {
645
+ if (this.originalPushState) return;
646
+ const check = () => {
647
+ const current = getCurrentPageId();
648
+ if (current !== this.lastPageId) {
649
+ this.lastPageId = current;
650
+ this.onChange(current);
651
+ }
652
+ };
653
+ this.popstateHandler = check;
654
+ window.addEventListener("popstate", this.popstateHandler);
655
+ this.hashchangeHandler = check;
656
+ window.addEventListener("hashchange", this.hashchangeHandler);
657
+ this.originalPushState = history.pushState.bind(history);
658
+ this.originalReplaceState = history.replaceState.bind(history);
659
+ history.pushState = (...args) => {
660
+ this.originalPushState(...args);
661
+ check();
662
+ };
663
+ history.replaceState = (...args) => {
664
+ this.originalReplaceState(...args);
665
+ check();
666
+ };
667
+ }
668
+ destroy() {
669
+ if (this.popstateHandler) {
670
+ window.removeEventListener("popstate", this.popstateHandler);
671
+ }
672
+ if (this.hashchangeHandler) {
673
+ window.removeEventListener("hashchange", this.hashchangeHandler);
674
+ }
675
+ if (this.originalPushState) {
676
+ history.pushState = this.originalPushState;
677
+ }
678
+ if (this.originalReplaceState) {
679
+ history.replaceState = this.originalReplaceState;
680
+ }
681
+ }
682
+ };
683
+
612
684
  // src/review/ReviewMode.ts
613
685
  var ReviewMode = class {
614
686
  constructor(token, shadowRoot, initData) {
@@ -622,6 +694,7 @@ var ReviewMode = class {
622
694
  __publicField(this, "promptEl", null);
623
695
  __publicField(this, "toolbarEl", null);
624
696
  __publicField(this, "clickHandler", null);
697
+ __publicField(this, "pageListener", null);
625
698
  this.pinManager = new PinManager();
626
699
  this.commentCard = new CommentCard(shadowRoot);
627
700
  this.nextPinNumber = initData.nextPinNumber;
@@ -632,6 +705,11 @@ var ReviewMode = class {
632
705
  this.showPrompt();
633
706
  this.renderToolbar();
634
707
  await this.loadExistingPins();
708
+ this.pinManager.filterByPage(getCurrentPageId());
709
+ this.pageListener = new PageNavigationListener((pageId) => {
710
+ this.pinManager.filterByPage(pageId);
711
+ });
712
+ this.pageListener.start();
635
713
  this.clickHandler = (e) => this.handleClick(e);
636
714
  document.addEventListener("click", this.clickHandler, true);
637
715
  }
@@ -676,7 +754,7 @@ var ReviewMode = class {
676
754
  for (const c of comments) {
677
755
  if (c.pinNumber != null && c.pinData) {
678
756
  const isMine = c.commentText != null;
679
- this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, isMine);
757
+ this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, isMine, c.pageUrl ?? "");
680
758
  }
681
759
  }
682
760
  } catch {
@@ -689,7 +767,7 @@ var ReviewMode = class {
689
767
  this.dismissPrompt();
690
768
  const pinData = captureElementContext(e.clientX, e.clientY);
691
769
  const pinNumber = this.nextPinNumber;
692
- this.pinManager.addPin(pinNumber, pinData.pinX, pinData.pinY, true);
770
+ this.pinManager.addPin(pinNumber, pinData.pinX, pinData.pinY, true, getCurrentPageId());
693
771
  this.pendingPinNumber = pinNumber;
694
772
  this.commentCard.show({
695
773
  x: e.clientX,
@@ -699,7 +777,7 @@ var ReviewMode = class {
699
777
  commentText: text,
700
778
  pinNumber,
701
779
  pinData,
702
- pageUrl: window.location.pathname
780
+ pageUrl: getCurrentPageId()
703
781
  });
704
782
  this.pendingPinNumber = null;
705
783
  this.nextPinNumber++;
@@ -722,7 +800,7 @@ var ReviewMode = class {
722
800
  commentText: text,
723
801
  pinNumber: null,
724
802
  pinData: null,
725
- pageUrl: window.location.pathname
803
+ pageUrl: getCurrentPageId()
726
804
  });
727
805
  this.showToast("Comment saved");
728
806
  },
@@ -742,6 +820,7 @@ var ReviewMode = class {
742
820
  if (this.clickHandler) {
743
821
  document.removeEventListener("click", this.clickHandler, true);
744
822
  }
823
+ this.pageListener?.destroy();
745
824
  this.dismissPrompt();
746
825
  this.toolbarEl?.remove();
747
826
  this.commentCard.hide();
@@ -749,6 +828,34 @@ var ReviewMode = class {
749
828
  }
750
829
  };
751
830
 
831
+ // src/review/TokenStorage.ts
832
+ var STORAGE_KEY = "ourroadmaps:token";
833
+ function storeToken(type, token) {
834
+ try {
835
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ type, token }));
836
+ } catch {
837
+ }
838
+ }
839
+ function getStoredToken() {
840
+ try {
841
+ const raw = sessionStorage.getItem(STORAGE_KEY);
842
+ if (!raw) return null;
843
+ const parsed = JSON.parse(raw);
844
+ if (parsed && typeof parsed.type === "string" && typeof parsed.token === "string") {
845
+ return parsed;
846
+ }
847
+ return null;
848
+ } catch {
849
+ return null;
850
+ }
851
+ }
852
+ function clearStoredToken() {
853
+ try {
854
+ sessionStorage.removeItem(STORAGE_KEY);
855
+ } catch {
856
+ }
857
+ }
858
+
752
859
  // src/review/TriageMode.ts
753
860
  var TriageMode = class {
754
861
  constructor(token, shadowRoot) {
@@ -759,6 +866,8 @@ var TriageMode = class {
759
866
  __publicField(this, "tooltipEl", null);
760
867
  __publicField(this, "comments", []);
761
868
  __publicField(this, "pinClickHandler", null);
869
+ __publicField(this, "pageListener", null);
870
+ __publicField(this, "showAllPages", false);
762
871
  this.pinManager = new PinManager();
763
872
  }
764
873
  async init() {
@@ -767,12 +876,20 @@ var TriageMode = class {
767
876
  this.comments = await fetchComments(this.token);
768
877
  for (const c of this.comments) {
769
878
  if (c.pinNumber != null && c.pinData) {
770
- this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, true);
879
+ this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, true, c.pageUrl ?? "");
771
880
  }
772
881
  }
773
882
  } catch {
774
883
  }
884
+ this.pinManager.filterByPage(getCurrentPageId());
775
885
  this.renderToolbar();
886
+ this.pageListener = new PageNavigationListener((pageId) => {
887
+ if (!this.showAllPages) {
888
+ this.pinManager.filterByPage(pageId);
889
+ }
890
+ this.updateToolbar();
891
+ });
892
+ this.pageListener.start();
776
893
  this.pinClickHandler = (e) => {
777
894
  const target = e.target;
778
895
  if (target.classList?.contains("review-pin")) {
@@ -786,13 +903,48 @@ var TriageMode = class {
786
903
  renderToolbar() {
787
904
  this.toolbarEl = document.createElement("div");
788
905
  this.toolbarEl.className = "review-toolbar";
789
- const count = this.comments.filter((c) => c.pinNumber != null).length;
790
- this.toolbarEl.innerHTML = `
791
- <span style="font-weight:500;">Triage Mode</span>
792
- <span style="opacity:0.7;">${count} pin${count !== 1 ? "s" : ""}</span>
793
- `;
906
+ this.updateToolbar();
794
907
  this.shadowRoot.appendChild(this.toolbarEl);
795
908
  }
909
+ updateToolbar() {
910
+ if (!this.toolbarEl) return;
911
+ const totalCount = this.comments.filter((c) => c.pinNumber != null).length;
912
+ const currentPageId = getCurrentPageId();
913
+ const pageCount = this.pinManager.countForPage(currentPageId);
914
+ const url = new URL(currentPageId, window.location.origin);
915
+ const pagePath = url.hash ? `${url.pathname}${url.hash.split("?")[0]}` : url.pathname || "/";
916
+ this.toolbarEl.innerHTML = "";
917
+ const label = document.createElement("span");
918
+ label.style.fontWeight = "500";
919
+ label.textContent = "Triage Mode";
920
+ this.toolbarEl.appendChild(label);
921
+ const count = document.createElement("span");
922
+ count.style.opacity = "0.7";
923
+ if (this.showAllPages) {
924
+ count.textContent = `${totalCount} pin${totalCount !== 1 ? "s" : ""}`;
925
+ } else {
926
+ count.textContent = `${pageCount} pin${pageCount !== 1 ? "s" : ""} on ${pagePath} (${totalCount} total)`;
927
+ }
928
+ this.toolbarEl.appendChild(count);
929
+ const toggleBtn = document.createElement("button");
930
+ toggleBtn.className = "review-btn review-btn--submit";
931
+ toggleBtn.style.cssText = "margin-left:auto;padding:6px 12px;font-size:13px;";
932
+ toggleBtn.textContent = this.showAllPages ? "This page" : "All pages";
933
+ toggleBtn.addEventListener("click", (e) => {
934
+ e.stopPropagation();
935
+ this.togglePageFilter();
936
+ });
937
+ this.toolbarEl.appendChild(toggleBtn);
938
+ }
939
+ togglePageFilter() {
940
+ this.showAllPages = !this.showAllPages;
941
+ if (this.showAllPages) {
942
+ this.pinManager.showAll();
943
+ } else {
944
+ this.pinManager.filterByPage(getCurrentPageId());
945
+ }
946
+ this.updateToolbar();
947
+ }
796
948
  handlePinClick(pinNumber, e) {
797
949
  this.hideTooltip();
798
950
  this.pinManager.highlightPin(pinNumber);
@@ -823,6 +975,25 @@ var TriageMode = class {
823
975
  timeEl.style.cssText = "font-size:11px;opacity:0.7;";
824
976
  timeEl.textContent = time;
825
977
  this.tooltipEl.appendChild(timeEl);
978
+ const pinPageUrl = comment.pageUrl ?? "";
979
+ if (pinPageUrl && pinPageUrl !== getCurrentPageId()) {
980
+ const navEl = document.createElement("div");
981
+ navEl.style.cssText = "margin-top:6px;padding-top:6px;border-top:1px solid rgba(255,255,255,0.2);font-size:12px;";
982
+ const pageLabel = document.createElement("span");
983
+ pageLabel.style.opacity = "0.7";
984
+ pageLabel.textContent = `Page: ${pinPageUrl.split("?")[0].split("#")[0] || "/"} \u2014 `;
985
+ navEl.appendChild(pageLabel);
986
+ const navLink = document.createElement("a");
987
+ navLink.style.cssText = "color:#a78bfa;cursor:pointer;text-decoration:underline;pointer-events:auto;";
988
+ navLink.textContent = "Navigate there?";
989
+ navLink.addEventListener("click", (e) => {
990
+ e.stopPropagation();
991
+ this.navigateToPin(pinPageUrl, pinNumber);
992
+ });
993
+ navEl.appendChild(navLink);
994
+ this.tooltipEl.appendChild(navEl);
995
+ this.tooltipEl.style.pointerEvents = "auto";
996
+ }
826
997
  const posX = x ?? window.innerWidth / 2;
827
998
  const posY = y ?? window.innerHeight / 3;
828
999
  this.tooltipEl.style.position = "fixed";
@@ -838,6 +1009,13 @@ var TriageMode = class {
838
1009
  };
839
1010
  setTimeout(() => document.addEventListener("click", dismiss, true), 0);
840
1011
  }
1012
+ navigateToPin(pageUrl, pinNumber) {
1013
+ this.hideTooltip();
1014
+ this.pinManager.clearHighlight();
1015
+ const url = new URL(pageUrl, window.location.origin);
1016
+ url.searchParams.set("pin", String(pinNumber));
1017
+ window.location.href = url.toString();
1018
+ }
841
1019
  autoFocusPin() {
842
1020
  const params = new URLSearchParams(window.location.search);
843
1021
  const pinParam = params.get("pin");
@@ -846,6 +1024,19 @@ var TriageMode = class {
846
1024
  if (!pinNumber) return;
847
1025
  const comment = this.comments.find((c) => c.pinNumber === pinNumber);
848
1026
  if (!comment?.pinData) return;
1027
+ const pinPageUrl = comment.pageUrl ?? "";
1028
+ if (pinPageUrl) {
1029
+ const currentUrl = new URL(window.location.href);
1030
+ currentUrl.searchParams.delete("pin");
1031
+ const currentPageWithoutPin = currentUrl.pathname + currentUrl.search + currentUrl.hash;
1032
+ if (pinPageUrl !== currentPageWithoutPin) {
1033
+ this.navigateToPin(pinPageUrl, pinNumber);
1034
+ return;
1035
+ }
1036
+ }
1037
+ this.pinManager.showAll();
1038
+ this.showAllPages = true;
1039
+ this.updateToolbar();
849
1040
  const docEl = document.documentElement;
850
1041
  const scrollX = comment.pinData.pinX / 100 * docEl.scrollWidth;
851
1042
  const scrollY = comment.pinData.pinY / 100 * docEl.scrollHeight;
@@ -867,6 +1058,7 @@ var TriageMode = class {
867
1058
  if (this.pinClickHandler) {
868
1059
  document.removeEventListener("click", this.pinClickHandler, true);
869
1060
  }
1061
+ this.pageListener?.destroy();
870
1062
  this.hideTooltip();
871
1063
  this.toolbarEl?.remove();
872
1064
  this.pinManager.destroy();
@@ -888,29 +1080,28 @@ var Review = class {
888
1080
  this.shadow.appendChild(styleEl);
889
1081
  document.body.appendChild(this.root);
890
1082
  }
891
- async init() {
892
- const params = new URLSearchParams(window.location.search);
893
- const reviewToken = params.get("review");
894
- const triageToken = params.get("triage");
895
- if (reviewToken) {
1083
+ async init(tokenType, token) {
1084
+ if (tokenType === "review") {
896
1085
  try {
897
- const data = await validateToken(reviewToken);
898
- this.mode = new ReviewMode(reviewToken, this.shadow, data);
1086
+ const data = await validateToken(token);
1087
+ this.mode = new ReviewMode(token, this.shadow, data);
899
1088
  await this.mode.init();
900
1089
  } catch (err) {
901
1090
  if (err instanceof ReviewError) {
1091
+ clearStoredToken();
902
1092
  this.showErrorOverlay(
903
1093
  err.code === "expired" ? "This feedback session has expired" : "This review link is no longer valid"
904
1094
  );
905
1095
  }
906
1096
  }
907
- } else if (triageToken) {
1097
+ } else if (tokenType === "triage") {
908
1098
  try {
909
- await validateToken(triageToken);
910
- this.mode = new TriageMode(triageToken, this.shadow);
1099
+ await validateToken(token);
1100
+ this.mode = new TriageMode(token, this.shadow);
911
1101
  await this.mode.init();
912
1102
  } catch (err) {
913
1103
  if (err instanceof ReviewError) {
1104
+ clearStoredToken();
914
1105
  this.showErrorOverlay(
915
1106
  err.code === "expired" ? "This feedback session has expired" : "This review link is no longer valid"
916
1107
  );
@@ -940,6 +1131,6 @@ var Review = class {
940
1131
  }
941
1132
  };
942
1133
 
943
- export { Review };
944
- //# sourceMappingURL=chunk-OHL77RGU.js.map
945
- //# sourceMappingURL=chunk-OHL77RGU.js.map
1134
+ export { Review, getStoredToken, storeToken };
1135
+ //# sourceMappingURL=chunk-266CLLX4.js.map
1136
+ //# sourceMappingURL=chunk-266CLLX4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/review/api.ts","../src/review/ElementCapture.ts","../src/review/styles.ts","../src/review/PinManager.ts","../src/review/CommentCard.ts","../src/review/PageTracker.ts","../src/review/ReviewMode.ts","../src/review/TokenStorage.ts","../src/review/TriageMode.ts","../src/review/Review.ts"],"names":[],"mappings":"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AgB;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;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","file":"chunk-266CLLX4.js","sourcesContent":["import type { PinData, ReviewComment, ReviewInitData } from './types'\n\n// Default to production, override with env var for local dev\nconst API_URL = (() => {\n // Vite dev mode\n if (typeof import.meta !== 'undefined' && import.meta.env?.VITE_API_URL) {\n return import.meta.env.VITE_API_URL\n }\n return 'https://api.ourroadmaps.com'\n})()\n\nexport async function validateToken(token: string): Promise<ReviewInitData> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}`)\n if (res.status === 410) throw new ReviewError('expired', 'This feedback session has expired')\n if (res.status === 404) throw new ReviewError('invalid', 'This review link is not valid')\n if (!res.ok) throw new ReviewError('error', 'Something went wrong')\n const body = await res.json()\n return body.data\n}\n\n// TODO: Wire into SDK — show name prompt on first visit, call identify() to transition invite status from 'pending' to 'opened'\nexport async function identify(token: string, name: string): Promise<void> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/identify`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name }),\n })\n if (!res.ok) throw new ReviewError('error', 'Failed to identify')\n}\n\nexport async function submitComment(\n token: string,\n comment: { commentText: string; pinNumber: number | null; pinData: PinData | null; pageUrl: string | null },\n): Promise<{ id: string; pinNumber: number | null; createdAt: string }> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(comment),\n })\n if (!res.ok) throw new ReviewError('error', 'Failed to submit comment')\n const body = await res.json()\n return body.data\n}\n\nexport async function fetchComments(token: string): Promise<ReviewComment[]> {\n const res = await fetch(`${API_URL}/v1/prototype-review/${token}/comments`)\n if (!res.ok) throw new ReviewError('error', 'Failed to load comments')\n const body = await res.json()\n return body.data\n}\n\nexport class ReviewError extends Error {\n constructor(\n public code: 'expired' | 'invalid' | 'error',\n message: string,\n ) {\n super(message)\n this.name = 'ReviewError'\n }\n}\n","import type { PinData } from './types'\n\nexport function captureElementContext(clientX: number, clientY: number): PinData {\n const el = document.elementFromPoint(clientX, clientY) as HTMLElement | null\n const docEl = document.documentElement\n\n const pinX = ((clientX + window.scrollX) / docEl.scrollWidth) * 100\n const pinY = ((clientY + window.scrollY) / docEl.scrollHeight) * 100\n\n if (!el || el === document.body || el === docEl) {\n return {\n pinX,\n pinY,\n element: {\n selector: 'body',\n tag: 'body',\n text: '',\n ariaLabel: null,\n className: '',\n boundingBox: { x: 0, y: 0, w: docEl.scrollWidth, h: docEl.scrollHeight },\n },\n context: {\n parentTag: '',\n parentText: '',\n grandparentTag: '',\n siblings: [],\n nearbyText: '',\n },\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n }\n\n const rect = el.getBoundingClientRect()\n\n return {\n pinX,\n pinY,\n element: {\n selector: buildSelector(el),\n tag: el.tagName.toLowerCase(),\n text: getDirectText(el).slice(0, 200),\n ariaLabel: el.getAttribute('aria-label'),\n className: el.className && typeof el.className === 'string' ? el.className : '',\n boundingBox: {\n x: Math.round(rect.x),\n y: Math.round(rect.y),\n w: Math.round(rect.width),\n h: Math.round(rect.height),\n },\n },\n context: {\n parentTag: el.parentElement ? `${el.parentElement.tagName.toLowerCase()}${classStr(el.parentElement)}` : '',\n parentText: el.parentElement ? getDirectText(el.parentElement).slice(0, 100) : '',\n grandparentTag: el.parentElement?.parentElement\n ? `${el.parentElement.parentElement.tagName.toLowerCase()}${classStr(el.parentElement.parentElement)}`\n : '',\n siblings: getSiblingsSummary(el),\n nearbyText: getNearbyText(el).slice(0, 200),\n },\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n }\n}\n\nfunction buildSelector(el: HTMLElement): string {\n const parts: string[] = []\n let current: HTMLElement | null = el\n\n while (current && current !== document.body) {\n if (current.id) {\n parts.unshift(`#${current.id}`)\n break\n }\n\n let part = current.tagName.toLowerCase()\n if (current.className && typeof current.className === 'string') {\n const classes = current.className.trim().split(/\\s+/).slice(0, 2).join('.')\n if (classes) part += `.${classes}`\n }\n\n parts.unshift(part)\n current = current.parentElement\n }\n\n return parts.join(' > ')\n}\n\nfunction getDirectText(el: HTMLElement): string {\n let text = ''\n for (const node of el.childNodes) {\n if (node.nodeType === Node.TEXT_NODE) {\n text += node.textContent?.trim() || ''\n }\n }\n return text.trim() || el.textContent?.trim().slice(0, 200) || ''\n}\n\nfunction classStr(el: HTMLElement): string {\n if (!el.className || typeof el.className !== 'string') return ''\n const cls = el.className.trim().split(/\\s+/).slice(0, 2).join('.')\n return cls ? `.${cls}` : ''\n}\n\nfunction getSiblingsSummary(el: HTMLElement): string[] {\n if (!el.parentElement) return []\n const siblings: string[] = []\n for (const child of el.parentElement.children) {\n if (child === el) continue\n const tag = child.tagName.toLowerCase()\n const text = (child.textContent?.trim() || '').slice(0, 50)\n siblings.push(text ? `${tag}:${text}` : tag)\n if (siblings.length >= 4) break\n }\n return siblings\n}\n\nfunction getNearbyText(el: HTMLElement): string {\n // Walk up to find the nearest container with meaningful text\n let current: HTMLElement | null = el.parentElement\n let depth = 0\n while (current && depth < 3) {\n const text = getDirectText(current)\n if (text && text !== getDirectText(el)) return text\n current = current.parentElement\n depth++\n }\n return ''\n}\n","export const REVIEW_STYLES = `\n:host {\n all: initial;\n}\n\n/* ─── Pin Container (rendered outside shadow DOM) ─── */\n.pin-container {\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 10000;\n}\n\n/* ─── Pin Marker ─── */\n.review-pin {\n position: absolute;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #7c3aed;\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n transform: translate(-50%, -50%);\n pointer-events: auto;\n cursor: pointer;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n user-select: none;\n z-index: 1;\n}\n\n.review-pin:hover {\n transform: translate(-50%, -50%) scale(1.15);\n box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);\n}\n\n.review-pin--other {\n opacity: 0.5;\n}\n\n.review-pin--highlighted {\n transform: translate(-50%, -50%) scale(1.2);\n box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);\n z-index: 2;\n}\n\n/* ─── Toolbar ─── */\n.review-toolbar {\n position: fixed;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n border-radius: 12px 12px 0 0;\n padding: 10px 20px;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n z-index: 10001;\n backdrop-filter: blur(8px);\n box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.2);\n}\n\n/* ─── Comment Card ─── */\n.review-comment-card {\n position: fixed;\n background: #fff;\n border-radius: 10px;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15), 0 1px 4px rgba(0, 0, 0, 0.08);\n width: 300px;\n padding: 16px;\n z-index: 10002;\n font-family: system-ui, -apple-system, sans-serif;\n border: 1px solid rgba(0, 0, 0, 0.08);\n}\n\n/* ─── Comment Textarea ─── */\n.review-comment-input {\n width: 100%;\n border: 1px solid #d1d5db;\n border-radius: 8px;\n padding: 10px 12px;\n resize: vertical;\n font-family: inherit;\n font-size: 14px;\n line-height: 1.5;\n color: #1f2937;\n background: #fafafa;\n box-sizing: border-box;\n outline: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.review-comment-input:focus {\n border-color: #7c3aed;\n box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.15);\n background: #fff;\n}\n\n.review-comment-input::placeholder {\n color: #9ca3af;\n}\n\n.review-comment-input:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* ─── Comment Actions ─── */\n.review-comment-actions {\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 12px;\n}\n\n/* ─── Buttons ─── */\n.review-btn {\n padding: 6px 14px;\n border-radius: 6px;\n cursor: pointer;\n font-family: inherit;\n font-size: 13px;\n font-weight: 500;\n border: none;\n transition: background 0.15s ease, opacity 0.15s ease;\n}\n\n.review-btn--cancel {\n color: #6b7280;\n background: transparent;\n}\n\n.review-btn--cancel:hover {\n background: #f3f4f6;\n}\n\n.review-btn--submit {\n background: #7c3aed;\n color: #fff;\n}\n\n.review-btn--submit:hover {\n background: #6d28d9;\n}\n\n.review-btn--submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* ─── Comment Error ─── */\n.review-comment-error {\n color: #dc2626;\n font-size: 12px;\n margin-top: 8px;\n font-family: system-ui, -apple-system, sans-serif;\n}\n\n/* ─── Name Prompt ─── */\n.review-prompt {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: #fff;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2), 0 1px 4px rgba(0, 0, 0, 0.08);\n border-radius: 12px;\n padding: 28px 32px;\n z-index: 10002;\n text-align: center;\n font-family: system-ui, -apple-system, sans-serif;\n border: 1px solid rgba(0, 0, 0, 0.06);\n max-width: 360px;\n width: 90%;\n}\n\n/* ─── Expired Overlay ─── */\n.review-expired-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10003;\n backdrop-filter: blur(2px);\n}\n\n.review-expired-card {\n background: #fff;\n border-radius: 12px;\n padding: 28px 32px;\n text-align: center;\n font-family: system-ui, -apple-system, sans-serif;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(0, 0, 0, 0.06);\n}\n\n/* ─── Toast ─── */\n.review-toast {\n position: fixed;\n bottom: 80px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n padding: 10px 20px;\n border-radius: 8px;\n z-index: 10003;\n pointer-events: none;\n animation: review-toast-fade 2.5s ease forwards;\n backdrop-filter: blur(8px);\n}\n\n@keyframes review-toast-fade {\n 0% { opacity: 0; transform: translateX(-50%) translateY(8px); }\n 10% { opacity: 1; transform: translateX(-50%) translateY(0); }\n 80% { opacity: 1; transform: translateX(-50%) translateY(0); }\n 100% { opacity: 0; transform: translateX(-50%) translateY(-4px); }\n}\n\n/* ─── Tooltip ─── */\n.review-tooltip {\n position: absolute;\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 13px;\n line-height: 1.4;\n border-radius: 6px;\n padding: 8px 12px;\n max-width: 280px;\n z-index: 10002;\n pointer-events: none;\n backdrop-filter: blur(8px);\n}\n\n/* ─── Reduced Motion ─── */\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n`\n\n/**\n * Subset of styles for pins that render outside the shadow DOM.\n * Injected into document.head by PinManager.\n */\nexport const PIN_DOCUMENT_STYLES = `\nbody {\n position: relative !important;\n}\n\n.pin-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n min-height: 100%;\n pointer-events: none;\n z-index: 10000;\n}\n\n.review-pin {\n position: absolute;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: #7c3aed;\n color: #fff;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 12px;\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n transform: translate(-50%, -50%);\n pointer-events: auto;\n cursor: pointer;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n user-select: none;\n z-index: 1;\n}\n\n.review-pin:hover {\n transform: translate(-50%, -50%) scale(1.15);\n box-shadow: 0 3px 10px rgba(124, 58, 237, 0.4);\n}\n\n.review-pin--other {\n opacity: 0.5;\n}\n\n.review-pin--highlighted {\n transform: translate(-50%, -50%) scale(1.2);\n box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3), 0 3px 10px rgba(124, 58, 237, 0.4);\n z-index: 2;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .review-pin, .review-pin:hover, .review-pin--highlighted {\n transition-duration: 0.01ms !important;\n }\n}\n`\n","import { PIN_DOCUMENT_STYLES } from './styles'\n\nconst STYLE_ID = 'roadmaps-review-pin-styles'\n\ninterface PinEntry {\n el: HTMLElement\n pageUrl: string\n}\n\nexport class PinManager {\n private container: HTMLDivElement\n private pins: Map<number, PinEntry> = new Map()\n private styleEl: HTMLStyleElement | null = null\n\n constructor() {\n this.container = document.createElement('div')\n this.container.className = 'pin-container'\n }\n\n mount(): void {\n if (!document.getElementById(STYLE_ID)) {\n this.styleEl = document.createElement('style')\n this.styleEl.id = STYLE_ID\n this.styleEl.textContent = PIN_DOCUMENT_STYLES\n document.head.appendChild(this.styleEl)\n }\n\n document.body.appendChild(this.container)\n }\n\n addPin(pinNumber: number, x: number, y: number, isMine: boolean, pageUrl: string): void {\n const pin = document.createElement('div')\n pin.className = isMine ? 'review-pin' : 'review-pin review-pin--other'\n pin.textContent = String(pinNumber)\n pin.style.left = `${x}%`\n pin.style.top = `${y}%`\n pin.dataset.pinNumber = String(pinNumber)\n pin.dataset.pageUrl = pageUrl\n this.container.appendChild(pin)\n this.pins.set(pinNumber, { el: pin, pageUrl })\n }\n\n removePin(pinNumber: number): void {\n const entry = this.pins.get(pinNumber)\n if (entry) {\n entry.el.remove()\n this.pins.delete(pinNumber)\n }\n }\n\n highlightPin(pinNumber: number): void {\n for (const [num, { el }] of this.pins) {\n el.classList.toggle('review-pin--highlighted', num === pinNumber)\n }\n }\n\n clearHighlight(): void {\n for (const { el } of this.pins.values()) {\n el.classList.remove('review-pin--highlighted')\n }\n }\n\n filterByPage(currentPageId: string): void {\n for (const { el, pageUrl } of this.pins.values()) {\n el.style.display = pageUrl === currentPageId ? '' : 'none'\n }\n }\n\n showAll(): void {\n for (const { el } of this.pins.values()) {\n el.style.display = ''\n }\n }\n\n countForPage(pageId: string): number {\n let count = 0\n for (const { pageUrl } of this.pins.values()) {\n if (pageUrl === pageId) count++\n }\n return count\n }\n\n destroy(): void {\n this.container.remove()\n this.pins.clear()\n\n if (this.styleEl) {\n this.styleEl.remove()\n this.styleEl = null\n } else {\n document.getElementById(STYLE_ID)?.remove()\n }\n }\n}\n","export class CommentCard {\n private card: HTMLElement | null = null\n private onSubmit: ((text: string) => Promise<void>) | null = null\n private onCancel: (() => void) | null = null\n\n constructor(private shadowRoot: ShadowRoot) {}\n\n show(options: {\n x: number\n y: number\n onSubmit: (text: string) => Promise<void>\n onCancel: () => void\n }): void {\n this.hide()\n this.onSubmit = options.onSubmit\n this.onCancel = options.onCancel\n\n this.card = document.createElement('div')\n this.card.className = 'review-comment-card'\n // Position near click point, adjust to stay in viewport\n this.card.style.left = `${Math.min(options.x, window.innerWidth - 320)}px`\n this.card.style.top = `${Math.min(options.y + 20, window.innerHeight - 200)}px`\n\n this.card.innerHTML = `\n <textarea class=\"review-comment-input\" placeholder=\"Leave your feedback...\" rows=\"3\"></textarea>\n <div class=\"review-comment-actions\">\n <button class=\"review-btn review-btn--cancel\">Cancel</button>\n <button class=\"review-btn review-btn--submit\">Submit</button>\n </div>\n <div class=\"review-comment-error\" style=\"display:none\"></div>\n `\n\n this.attachListeners()\n this.shadowRoot.appendChild(this.card)\n\n const textarea = this.card.querySelector('textarea')\n textarea?.focus()\n }\n\n hide(): void {\n this.card?.remove()\n this.card = null\n }\n\n showForGeneral(options: {\n onSubmit: (text: string) => Promise<void>\n onCancel: () => void\n }): void {\n // Show centered for general (non-pinned) comments\n this.show({\n x: window.innerWidth / 2 - 150,\n y: window.innerHeight / 2 - 100,\n onSubmit: options.onSubmit,\n onCancel: options.onCancel,\n })\n }\n\n private attachListeners(): void {\n if (!this.card) return\n\n this.card.querySelector('.review-btn--cancel')?.addEventListener('click', () => {\n this.onCancel?.()\n this.hide()\n })\n\n this.card.querySelector('.review-btn--submit')?.addEventListener('click', () => this.handleSubmit())\n\n this.card.querySelector('textarea')?.addEventListener('keydown', (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n this.handleSubmit()\n }\n })\n }\n\n private async handleSubmit(): Promise<void> {\n if (!this.card) return\n const textarea = this.card.querySelector('textarea') as HTMLTextAreaElement\n const text = textarea.value.trim()\n if (!text) return\n\n const submitBtn = this.card.querySelector('.review-btn--submit') as HTMLButtonElement\n const errorEl = this.card.querySelector('.review-comment-error') as HTMLElement\n submitBtn.disabled = true\n submitBtn.textContent = 'Submitting...'\n textarea.disabled = true\n errorEl.style.display = 'none'\n\n try {\n await this.onSubmit?.(text)\n this.hide()\n } catch (err) {\n submitBtn.disabled = false\n submitBtn.textContent = 'Submit'\n textarea.disabled = false\n errorEl.textContent = err instanceof Error ? err.message : 'Failed to submit'\n errorEl.style.display = 'block'\n }\n }\n}\n","/**\n * Returns the current page identity as pathname + search + hash.\n * This captures all routing strategies: pathname, hash, and query-param based.\n */\nexport function getCurrentPageId(): string {\n return window.location.pathname + window.location.search + window.location.hash\n}\n\n/**\n * Detects page navigation within a prototype.\n * Covers: popstate (back/forward), hashchange (hash routing),\n * and pushState/replaceState (SPA routing via history API).\n */\nexport class PageNavigationListener {\n private popstateHandler: (() => void) | null = null\n private hashchangeHandler: (() => void) | null = null\n private originalPushState: typeof history.pushState | null = null\n private originalReplaceState: typeof history.replaceState | null = null\n private lastPageId: string\n\n constructor(private onChange: (pageId: string) => void) {\n this.lastPageId = getCurrentPageId()\n }\n\n start(): void {\n // Guard against double-start — would capture already-patched history methods\n if (this.originalPushState) return\n\n const check = () => {\n const current = getCurrentPageId()\n if (current !== this.lastPageId) {\n this.lastPageId = current\n this.onChange(current)\n }\n }\n\n this.popstateHandler = check\n window.addEventListener('popstate', this.popstateHandler)\n\n this.hashchangeHandler = check\n window.addEventListener('hashchange', this.hashchangeHandler)\n\n this.originalPushState = history.pushState.bind(history)\n this.originalReplaceState = history.replaceState.bind(history)\n\n history.pushState = (...args: Parameters<typeof history.pushState>) => {\n this.originalPushState!(...args)\n check()\n }\n\n history.replaceState = (...args: Parameters<typeof history.replaceState>) => {\n this.originalReplaceState!(...args)\n check()\n }\n }\n\n destroy(): void {\n if (this.popstateHandler) {\n window.removeEventListener('popstate', this.popstateHandler)\n }\n if (this.hashchangeHandler) {\n window.removeEventListener('hashchange', this.hashchangeHandler)\n }\n\n if (this.originalPushState) {\n history.pushState = this.originalPushState\n }\n if (this.originalReplaceState) {\n history.replaceState = this.originalReplaceState\n }\n }\n}\n","import { submitComment, fetchComments } from './api'\nimport { captureElementContext } from './ElementCapture'\nimport { PinManager } from './PinManager'\nimport { CommentCard } from './CommentCard'\nimport type { ReviewInitData } from './types'\nimport { getCurrentPageId, PageNavigationListener } from './PageTracker'\n\nexport class ReviewMode {\n private pinManager: PinManager\n private commentCard: CommentCard\n private nextPinNumber: number\n private pendingPinNumber: number | null = null\n private promptEl: HTMLElement | null = null\n private toolbarEl: HTMLElement | null = null\n private clickHandler: ((e: MouseEvent) => void) | null = null\n private pageListener: PageNavigationListener | null = null\n\n constructor(\n private token: string,\n private shadowRoot: ShadowRoot,\n private initData: ReviewInitData,\n ) {\n this.pinManager = new PinManager()\n this.commentCard = new CommentCard(shadowRoot)\n this.nextPinNumber = initData.nextPinNumber\n }\n\n async init(): Promise<void> {\n // Set crosshair cursor on body\n document.body.style.cursor = 'crosshair'\n\n // Mount pin manager\n this.pinManager.mount()\n\n // Render first-visit prompt\n this.showPrompt()\n\n // Render bottom toolbar\n this.renderToolbar()\n\n // Fetch and render existing pins\n await this.loadExistingPins()\n\n // Filter pins to current page and listen for navigation\n this.pinManager.filterByPage(getCurrentPageId())\n this.pageListener = new PageNavigationListener((pageId) => {\n this.pinManager.filterByPage(pageId)\n })\n this.pageListener.start()\n\n // Listen for clicks to drop pins\n this.clickHandler = (e: MouseEvent) => this.handleClick(e)\n document.addEventListener('click', this.clickHandler, true)\n }\n\n private showPrompt(): void {\n this.promptEl = document.createElement('div')\n this.promptEl.className = 'review-prompt'\n this.promptEl.innerHTML = `\n <h3 style=\"margin:0 0 8px;font-size:16px;\">Click anywhere to leave feedback</h3>\n <p style=\"margin:0;color:#666;font-size:14px;\">Drop numbered pins on elements you want to comment on</p>\n `\n this.shadowRoot.appendChild(this.promptEl)\n\n // Auto-dismiss after 5 seconds\n setTimeout(() => this.dismissPrompt(), 5000)\n }\n\n private dismissPrompt(): void {\n if (this.promptEl) {\n this.promptEl.remove()\n this.promptEl = null\n }\n }\n\n private renderToolbar(): void {\n this.toolbarEl = document.createElement('div')\n this.toolbarEl.className = 'review-toolbar'\n this.updateToolbar()\n this.shadowRoot.appendChild(this.toolbarEl)\n }\n\n private updateToolbar(): void {\n if (!this.toolbarEl) return\n const pinCount = this.nextPinNumber - 1\n this.toolbarEl.innerHTML = `\n <span style=\"font-weight:500;\">${this.initData.session.name}</span>\n <span style=\"opacity:0.7;\">${pinCount} pin${pinCount !== 1 ? 's' : ''}</span>\n <button class=\"review-btn review-btn--submit\" style=\"margin-left:auto;padding:6px 12px;font-size:13px;\">General Comment</button>\n `\n this.toolbarEl.querySelector('button')?.addEventListener('click', (e) => {\n e.stopPropagation()\n this.handleGeneralComment()\n })\n }\n\n private async loadExistingPins(): Promise<void> {\n try {\n const comments = await fetchComments(this.token)\n for (const c of comments) {\n if (c.pinNumber != null && c.pinData) {\n // commentText being non-null means it's the current reviewer's pin\n const isMine = c.commentText != null\n this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, isMine, c.pageUrl ?? '')\n }\n }\n } catch {\n // Silently fail - pins just won't show up\n }\n }\n\n private handleClick(e: MouseEvent): void {\n // Don't intercept clicks on our own shadow DOM elements\n const path = e.composedPath()\n if (path.some((el) => el instanceof HTMLElement && el.closest?.('#ourroadmaps-review'))) return\n\n // Don't intercept if comment card is open\n if (this.pendingPinNumber != null) return\n\n // Dismiss first-visit prompt on first click\n this.dismissPrompt()\n\n const pinData = captureElementContext(e.clientX, e.clientY)\n const pinNumber = this.nextPinNumber\n\n // Add pin at click position\n this.pinManager.addPin(pinNumber, pinData.pinX, pinData.pinY, true, getCurrentPageId())\n this.pendingPinNumber = pinNumber\n\n // Show comment card\n this.commentCard.show({\n x: e.clientX,\n y: e.clientY,\n onSubmit: async (text: string) => {\n await submitComment(this.token, {\n commentText: text,\n pinNumber,\n pinData,\n pageUrl: getCurrentPageId(),\n })\n this.pendingPinNumber = null\n this.nextPinNumber++\n this.updateToolbar()\n this.showToast('Comment saved')\n },\n onCancel: () => {\n // Remove the pending pin\n this.pinManager.removePin(pinNumber)\n this.pendingPinNumber = null\n },\n })\n\n e.preventDefault()\n e.stopPropagation()\n }\n\n private handleGeneralComment(): void {\n if (this.pendingPinNumber != null) return\n\n this.commentCard.showForGeneral({\n onSubmit: async (text: string) => {\n await submitComment(this.token, {\n commentText: text,\n pinNumber: null,\n pinData: null,\n pageUrl: getCurrentPageId(),\n })\n this.showToast('Comment saved')\n },\n onCancel: () => {},\n })\n }\n\n private showToast(message: string): void {\n const toast = document.createElement('div')\n toast.className = 'review-toast'\n toast.textContent = message\n this.shadowRoot.appendChild(toast)\n setTimeout(() => toast.remove(), 2500)\n }\n\n destroy(): void {\n document.body.style.cursor = ''\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler, true)\n }\n this.pageListener?.destroy()\n this.dismissPrompt()\n this.toolbarEl?.remove()\n this.commentCard.hide()\n this.pinManager.destroy()\n }\n}\n","const STORAGE_KEY = 'ourroadmaps:token'\n\ninterface StoredToken {\n type: 'review' | 'triage'\n token: string\n}\n\nexport function storeToken(type: 'review' | 'triage', token: string): void {\n try {\n sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ type, token }))\n } catch {\n // sessionStorage unavailable (private browsing, storage full, etc.)\n }\n}\n\nexport function getStoredToken(): StoredToken | null {\n try {\n const raw = sessionStorage.getItem(STORAGE_KEY)\n if (!raw) return null\n const parsed = JSON.parse(raw)\n if (parsed && typeof parsed.type === 'string' && typeof parsed.token === 'string') {\n return parsed as StoredToken\n }\n return null\n } catch {\n return null\n }\n}\n\nexport function clearStoredToken(): void {\n try {\n sessionStorage.removeItem(STORAGE_KEY)\n } catch {\n // sessionStorage unavailable\n }\n}\n","import { fetchComments } from './api'\nimport { PinManager } from './PinManager'\nimport { getCurrentPageId, PageNavigationListener } from './PageTracker'\nimport type { ReviewComment } from './types'\n\nexport class TriageMode {\n private pinManager: PinManager\n private toolbarEl: HTMLElement | null = null\n private tooltipEl: HTMLElement | null = null\n private comments: ReviewComment[] = []\n private pinClickHandler: ((e: MouseEvent) => void) | null = null\n private pageListener: PageNavigationListener | null = null\n private showAllPages = false\n\n constructor(\n private token: string,\n private shadowRoot: ShadowRoot,\n ) {\n this.pinManager = new PinManager()\n }\n\n async init(): Promise<void> {\n this.pinManager.mount()\n\n try {\n this.comments = await fetchComments(this.token)\n for (const c of this.comments) {\n if (c.pinNumber != null && c.pinData) {\n this.pinManager.addPin(c.pinNumber, c.pinData.pinX, c.pinData.pinY, true, c.pageUrl ?? '')\n }\n }\n } catch {\n // Show error state\n }\n\n this.pinManager.filterByPage(getCurrentPageId())\n\n this.renderToolbar()\n\n this.pageListener = new PageNavigationListener((pageId) => {\n if (!this.showAllPages) {\n this.pinManager.filterByPage(pageId)\n }\n this.updateToolbar()\n })\n this.pageListener.start()\n\n this.pinClickHandler = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n if (target.classList?.contains('review-pin')) {\n const num = Number(target.dataset.pinNumber)\n if (num) this.handlePinClick(num, e)\n }\n }\n document.addEventListener('click', this.pinClickHandler, true)\n\n this.autoFocusPin()\n }\n\n private renderToolbar(): void {\n this.toolbarEl = document.createElement('div')\n this.toolbarEl.className = 'review-toolbar'\n this.updateToolbar()\n this.shadowRoot.appendChild(this.toolbarEl)\n }\n\n private updateToolbar(): void {\n if (!this.toolbarEl) return\n\n const totalCount = this.comments.filter((c) => c.pinNumber != null).length\n const currentPageId = getCurrentPageId()\n const pageCount = this.pinManager.countForPage(currentPageId)\n // Show pathname for path-routed apps, or pathname#hash for hash-routed apps\n const url = new URL(currentPageId, window.location.origin)\n const pagePath = url.hash ? `${url.pathname}${url.hash.split('?')[0]}` : (url.pathname || '/')\n\n this.toolbarEl.innerHTML = ''\n\n const label = document.createElement('span')\n label.style.fontWeight = '500'\n label.textContent = 'Triage Mode'\n this.toolbarEl.appendChild(label)\n\n const count = document.createElement('span')\n count.style.opacity = '0.7'\n if (this.showAllPages) {\n count.textContent = `${totalCount} pin${totalCount !== 1 ? 's' : ''}`\n } else {\n count.textContent = `${pageCount} pin${pageCount !== 1 ? 's' : ''} on ${pagePath} (${totalCount} total)`\n }\n this.toolbarEl.appendChild(count)\n\n const toggleBtn = document.createElement('button')\n toggleBtn.className = 'review-btn review-btn--submit'\n toggleBtn.style.cssText = 'margin-left:auto;padding:6px 12px;font-size:13px;'\n toggleBtn.textContent = this.showAllPages ? 'This page' : 'All pages'\n toggleBtn.addEventListener('click', (e) => {\n e.stopPropagation()\n this.togglePageFilter()\n })\n this.toolbarEl.appendChild(toggleBtn)\n }\n\n private togglePageFilter(): void {\n this.showAllPages = !this.showAllPages\n if (this.showAllPages) {\n this.pinManager.showAll()\n } else {\n this.pinManager.filterByPage(getCurrentPageId())\n }\n this.updateToolbar()\n }\n\n private handlePinClick(pinNumber: number, e: MouseEvent): void {\n this.hideTooltip()\n this.pinManager.highlightPin(pinNumber)\n this.showTooltipForPin(pinNumber, e.clientX + 16, e.clientY - 10)\n }\n\n private showTooltipForPin(pinNumber: number, x?: number, y?: number): void {\n this.hideTooltip()\n\n const comment = this.comments.find((c) => c.pinNumber === pinNumber)\n if (!comment) return\n\n this.tooltipEl = document.createElement('div')\n this.tooltipEl.className = 'review-tooltip'\n\n const time = new Date(comment.createdAt).toLocaleString()\n\n const header = document.createElement('div')\n header.style.cssText = 'font-weight:500;margin-bottom:4px;'\n header.textContent = `Pin #${pinNumber}`\n this.tooltipEl.appendChild(header)\n\n if (comment.reviewerName) {\n const name = document.createElement('div')\n name.style.cssText = 'font-size:11px;opacity:0.7;margin-bottom:2px;'\n name.textContent = comment.reviewerName\n this.tooltipEl.appendChild(name)\n }\n\n const text = document.createElement('div')\n text.style.cssText = 'margin-bottom:4px;'\n text.textContent = comment.commentText || '(no text)'\n this.tooltipEl.appendChild(text)\n\n const timeEl = document.createElement('div')\n timeEl.style.cssText = 'font-size:11px;opacity:0.7;'\n timeEl.textContent = time\n this.tooltipEl.appendChild(timeEl)\n\n const pinPageUrl = comment.pageUrl ?? ''\n if (pinPageUrl && pinPageUrl !== getCurrentPageId()) {\n const navEl = document.createElement('div')\n navEl.style.cssText = 'margin-top:6px;padding-top:6px;border-top:1px solid rgba(255,255,255,0.2);font-size:12px;'\n\n const pageLabel = document.createElement('span')\n pageLabel.style.opacity = '0.7'\n pageLabel.textContent = `Page: ${pinPageUrl.split('?')[0].split('#')[0] || '/'} \\u2014 `\n navEl.appendChild(pageLabel)\n\n const navLink = document.createElement('a')\n navLink.style.cssText = 'color:#a78bfa;cursor:pointer;text-decoration:underline;pointer-events:auto;'\n navLink.textContent = 'Navigate there?'\n navLink.addEventListener('click', (e) => {\n e.stopPropagation()\n this.navigateToPin(pinPageUrl, pinNumber)\n })\n navEl.appendChild(navLink)\n\n this.tooltipEl.appendChild(navEl)\n this.tooltipEl.style.pointerEvents = 'auto'\n }\n\n const posX = x ?? window.innerWidth / 2\n const posY = y ?? window.innerHeight / 3\n this.tooltipEl.style.position = 'fixed'\n this.tooltipEl.style.left = `${Math.min(posX, window.innerWidth - 300)}px`\n this.tooltipEl.style.top = `${Math.min(posY, window.innerHeight - 150)}px`\n this.shadowRoot.appendChild(this.tooltipEl)\n\n const dismiss = (ev: MouseEvent) => {\n if (ev.target !== this.tooltipEl && !this.tooltipEl?.contains(ev.target as Node)) {\n this.hideTooltip()\n this.pinManager.clearHighlight()\n document.removeEventListener('click', dismiss, true)\n }\n }\n setTimeout(() => document.addEventListener('click', dismiss, true), 0)\n }\n\n private navigateToPin(pageUrl: string, pinNumber: number): void {\n this.hideTooltip()\n this.pinManager.clearHighlight()\n\n const url = new URL(pageUrl, window.location.origin)\n url.searchParams.set('pin', String(pinNumber))\n window.location.href = url.toString()\n }\n\n private autoFocusPin(): void {\n const params = new URLSearchParams(window.location.search)\n const pinParam = params.get('pin')\n if (!pinParam) return\n\n const pinNumber = Number(pinParam)\n if (!pinNumber) return\n\n const comment = this.comments.find((c) => c.pinNumber === pinNumber)\n if (!comment?.pinData) return\n\n const pinPageUrl = comment.pageUrl ?? ''\n if (pinPageUrl) {\n // Strip ?pin=N from current URL before comparing — navigateToPin adds it,\n // so after redirect getCurrentPageId() includes it but comment.pageUrl doesn't\n const currentUrl = new URL(window.location.href)\n currentUrl.searchParams.delete('pin')\n const currentPageWithoutPin = currentUrl.pathname + currentUrl.search + currentUrl.hash\n if (pinPageUrl !== currentPageWithoutPin) {\n this.navigateToPin(pinPageUrl, pinNumber)\n return\n }\n }\n\n this.pinManager.showAll()\n this.showAllPages = true\n this.updateToolbar()\n\n const docEl = document.documentElement\n const scrollX = (comment.pinData.pinX / 100) * docEl.scrollWidth\n const scrollY = (comment.pinData.pinY / 100) * docEl.scrollHeight\n\n window.scrollTo({\n left: scrollX - window.innerWidth / 2,\n top: scrollY - window.innerHeight / 2,\n behavior: 'smooth',\n })\n\n setTimeout(() => {\n this.pinManager.highlightPin(pinNumber)\n this.showTooltipForPin(pinNumber)\n }, 500)\n }\n\n private hideTooltip(): void {\n this.tooltipEl?.remove()\n this.tooltipEl = null\n }\n\n destroy(): void {\n if (this.pinClickHandler) {\n document.removeEventListener('click', this.pinClickHandler, true)\n }\n this.pageListener?.destroy()\n this.hideTooltip()\n this.toolbarEl?.remove()\n this.pinManager.destroy()\n }\n}\n","import { validateToken, ReviewError } from './api'\nimport { ReviewMode } from './ReviewMode'\nimport { clearStoredToken } from './TokenStorage'\nimport { TriageMode } from './TriageMode'\nimport { REVIEW_STYLES } from './styles'\nimport type { ReviewOptions } from './types'\n\nexport class Review {\n private root: HTMLElement\n private shadow: ShadowRoot\n private mode: ReviewMode | TriageMode | null = null\n private _isDestroyed = false\n\n constructor(_options: ReviewOptions = {}) {\n this.root = document.createElement('div')\n this.root.id = 'ourroadmaps-review'\n this.shadow = this.root.attachShadow({ mode: 'open' })\n\n const styleEl = document.createElement('style')\n styleEl.textContent = REVIEW_STYLES\n this.shadow.appendChild(styleEl)\n\n document.body.appendChild(this.root)\n }\n\n async init(tokenType: 'review' | 'triage', token: string): Promise<void> {\n if (tokenType === 'review') {\n try {\n const data = await validateToken(token)\n this.mode = new ReviewMode(token, this.shadow, data)\n await this.mode.init()\n } catch (err) {\n if (err instanceof ReviewError) {\n clearStoredToken()\n this.showErrorOverlay(\n err.code === 'expired'\n ? 'This feedback session has expired'\n : 'This review link is no longer valid',\n )\n }\n }\n } else if (tokenType === 'triage') {\n try {\n await validateToken(token)\n this.mode = new TriageMode(token, this.shadow)\n await this.mode.init()\n } catch (err) {\n if (err instanceof ReviewError) {\n clearStoredToken()\n this.showErrorOverlay(\n err.code === 'expired'\n ? 'This feedback session has expired'\n : 'This review link is no longer valid',\n )\n }\n }\n }\n }\n\n private showErrorOverlay(message: string): void {\n const overlay = document.createElement('div')\n overlay.className = 'review-expired-overlay'\n overlay.innerHTML = `\n <div class=\"review-expired-card\">\n <h2 style=\"margin:0 0 8px;font-size:18px;\">Session Unavailable</h2>\n <p style=\"margin:0;color:#666;font-size:14px;\">${message}</p>\n <p style=\"margin:12px 0 0;color:#999;font-size:13px;\">Contact the prototype owner for a new link.</p>\n </div>\n `\n this.shadow.appendChild(overlay)\n\n // Apply grayscale to body\n document.body.style.filter = 'grayscale(0.8)'\n }\n\n destroy(): void {\n if (this._isDestroyed) return\n this._isDestroyed = true\n this.mode?.destroy()\n this.root.remove()\n document.body.style.filter = ''\n }\n}\n"]}