web-remarq 0.6.0 → 0.7.2

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.
@@ -1,10 +1,8 @@
1
1
  "use strict";
2
- var WebRemarq = (() => {
2
+ (() => {
3
3
  var __defProp = Object.defineProperty;
4
4
  var __defProps = Object.defineProperties;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
5
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
- var __getOwnPropNames = Object.getOwnPropertyNames;
8
6
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
8
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
@@ -33,26 +31,6 @@ var WebRemarq = (() => {
33
31
  }
34
32
  return target;
35
33
  };
36
- var __export = (target, all) => {
37
- for (var name in all)
38
- __defProp(target, name, { get: all[name], enumerable: true });
39
- };
40
- var __copyProps = (to, from, except, desc) => {
41
- if (from && typeof from === "object" || typeof from === "function") {
42
- for (let key of __getOwnPropNames(from))
43
- if (!__hasOwnProp.call(to, key) && key !== except)
44
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
45
- }
46
- return to;
47
- };
48
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
49
-
50
- // src/index.ts
51
- var src_exports = {};
52
- __export(src_exports, {
53
- LocalStorageAdapter: () => LocalStorageAdapter,
54
- WebRemarq: () => WebRemarq
55
- });
56
34
 
57
35
  // src/core/viewport.ts
58
36
  var currentBucket = 0;
@@ -89,6 +67,21 @@ var WebRemarq = (() => {
89
67
  }
90
68
 
91
69
  // src/core/storage.ts
70
+ function migrateAnnotation(legacy) {
71
+ const rawStatus = legacy.status;
72
+ const status = rawStatus === "resolved" ? "verified" : rawStatus;
73
+ if (Array.isArray(legacy.lifecycle) && legacy.lifecycle.length > 0) {
74
+ return __spreadProps(__spreadValues({}, legacy), { status, lifecycle: legacy.lifecycle });
75
+ }
76
+ const createdTs = typeof legacy.timestamp === "number" ? legacy.timestamp : Date.now();
77
+ const lifecycle = [
78
+ { type: "created", actor: "designer", timestamp: createdTs }
79
+ ];
80
+ if (rawStatus === "resolved") {
81
+ lifecycle.push({ type: "migrated", actor: null, timestamp: Date.now() });
82
+ }
83
+ return __spreadProps(__spreadValues({}, legacy), { status, lifecycle });
84
+ }
92
85
  var AnnotationStorage = class {
93
86
  constructor(adapter) {
94
87
  this.adapter = adapter;
@@ -105,6 +98,9 @@ var WebRemarq = (() => {
105
98
  getByRoute(route) {
106
99
  return this.cache.filter((a) => a.route === route);
107
100
  }
101
+ getById(id) {
102
+ return this.cache.find((a) => a.id === id);
103
+ }
108
104
  async add(annotation) {
109
105
  this.cache.push(annotation);
110
106
  await this.adapter.save(annotation);
@@ -131,7 +127,7 @@ var WebRemarq = (() => {
131
127
  };
132
128
  }
133
129
  async importJSON(data) {
134
- this.cache = [...data.annotations];
130
+ this.cache = data.annotations.map(migrateAnnotation);
135
131
  this.migrateViewportBuckets();
136
132
  await this.adapter.clear();
137
133
  for (const ann of this.cache) {
@@ -141,7 +137,7 @@ var WebRemarq = (() => {
141
137
  async init() {
142
138
  const data = await this.adapter.load();
143
139
  if (data) {
144
- this.cache = data.annotations;
140
+ this.cache = data.annotations.map(migrateAnnotation);
145
141
  this.migrateViewportBuckets();
146
142
  }
147
143
  }
@@ -611,7 +607,16 @@ var WebRemarq = (() => {
611
607
  status: ann.status,
612
608
  timestamp: ann.timestamp,
613
609
  source: resolveSource(ann.fingerprint),
614
- searchHints: buildSearchHints(ann.fingerprint)
610
+ searchHints: buildSearchHints(ann.fingerprint),
611
+ lifecycle: ann.lifecycle.map((ev) => {
612
+ const out = {
613
+ type: ev.type,
614
+ actor: ev.actor,
615
+ timestamp: ev.timestamp
616
+ };
617
+ if (ev.reason !== void 0) out.reason = ev.reason;
618
+ return out;
619
+ })
615
620
  }));
616
621
  return {
617
622
  version: 1,
@@ -621,6 +626,67 @@ var WebRemarq = (() => {
621
626
  };
622
627
  }
623
628
 
629
+ // src/core/lifecycle.ts
630
+ var InvalidTransitionError = class extends Error {
631
+ constructor(from, action) {
632
+ super(`Cannot ${action} from status "${from}"`);
633
+ this.name = "InvalidTransitionError";
634
+ }
635
+ };
636
+ var ACTION_TO_EVENT = {
637
+ acknowledge: "acknowledged",
638
+ claimFix: "fix_claimed",
639
+ verify: "verified",
640
+ reject: "rejected",
641
+ dismiss: "dismissed",
642
+ reopen: "reopened"
643
+ };
644
+ var DEFAULT_ACTOR_BY_EVENT = {
645
+ created: "designer",
646
+ acknowledged: "developer",
647
+ fix_claimed: "agent",
648
+ verified: "developer",
649
+ rejected: "developer",
650
+ dismissed: "developer",
651
+ reopened: "developer",
652
+ migrated: null
653
+ };
654
+ function createEvent(type, opts = {}) {
655
+ var _a3, _b;
656
+ const event = {
657
+ type,
658
+ actor: (_a3 = opts.actor) != null ? _a3 : DEFAULT_ACTOR_BY_EVENT[type],
659
+ timestamp: (_b = opts.timestamp) != null ? _b : Date.now()
660
+ };
661
+ if (opts.actorName !== void 0) event.actorName = opts.actorName;
662
+ if (opts.reason !== void 0) event.reason = opts.reason;
663
+ return event;
664
+ }
665
+ function nextStatus(from, action) {
666
+ switch (action) {
667
+ case "acknowledge":
668
+ return from === "pending" ? "in_progress" : null;
669
+ case "claimFix":
670
+ return from === "pending" || from === "in_progress" ? "fixed_unverified" : null;
671
+ case "verify":
672
+ return from === "fixed_unverified" || from === "in_progress" ? "verified" : null;
673
+ case "reject":
674
+ return from === "fixed_unverified" ? "pending" : null;
675
+ case "dismiss":
676
+ return from === "pending" || from === "in_progress" || from === "fixed_unverified" ? "dismissed" : null;
677
+ case "reopen":
678
+ return from === "dismissed" || from === "verified" ? "pending" : null;
679
+ }
680
+ }
681
+ function transition(annotation, action, opts = {}) {
682
+ const next = nextStatus(annotation.status, action);
683
+ if (next === null) {
684
+ throw new InvalidTransitionError(annotation.status, action);
685
+ }
686
+ const event = createEvent(ACTION_TO_EVENT[action], opts);
687
+ return { status: next, event };
688
+ }
689
+
624
690
  // src/ui/styles.ts
625
691
  var STYLES_ID = "data-remarq-styles";
626
692
  var CSS = `
@@ -635,6 +701,11 @@ var WebRemarq = (() => {
635
701
  --remarq-resolved: #22c55e;
636
702
  --remarq-overlay: rgba(59, 130, 246, 0.15);
637
703
  --remarq-shadow: 0 4px 12px rgba(0,0,0,0.15);
704
+ --remarq-status-pending: #f97316;
705
+ --remarq-status-in-progress: #eab308;
706
+ --remarq-status-fixed-unverified: #3b82f6;
707
+ --remarq-status-verified: #22c55e;
708
+ --remarq-status-dismissed: #6b7280;
638
709
  }
639
710
 
640
711
  [data-remarq-theme="dark"] {
@@ -648,6 +719,11 @@ var WebRemarq = (() => {
648
719
  --remarq-resolved: #4ade80;
649
720
  --remarq-overlay: rgba(96, 165, 250, 0.15);
650
721
  --remarq-shadow: 0 4px 12px rgba(0,0,0,0.4);
722
+ --remarq-status-pending: #fb923c;
723
+ --remarq-status-in-progress: #facc15;
724
+ --remarq-status-fixed-unverified: #60a5fa;
725
+ --remarq-status-verified: #4ade80;
726
+ --remarq-status-dismissed: #9ca3af;
651
727
  }
652
728
 
653
729
  .remarq-toolbar {
@@ -691,19 +767,29 @@ var WebRemarq = (() => {
691
767
 
692
768
  .remarq-badge {
693
769
  position: absolute;
694
- top: -4px;
695
- right: -4px;
696
- min-width: 16px;
697
- height: 16px;
698
- padding: 0 4px;
699
- border-radius: 8px;
770
+ top: -6px;
771
+ right: -6px;
772
+ width: 18px;
773
+ height: 18px;
774
+ padding: 0;
775
+ border-radius: 50%;
700
776
  background: var(--remarq-pending);
701
777
  color: #ffffff;
702
778
  font-size: 10px;
703
779
  font-weight: 600;
780
+ line-height: 1;
704
781
  display: flex;
705
782
  align-items: center;
706
783
  justify-content: center;
784
+ box-sizing: border-box;
785
+ }
786
+
787
+ .remarq-toolbar-badge--verification {
788
+ top: auto;
789
+ bottom: -6px;
790
+ right: -6px;
791
+ background: var(--remarq-status-fixed-unverified);
792
+ cursor: pointer;
707
793
  }
708
794
 
709
795
  .remarq-overlay {
@@ -753,8 +839,11 @@ var WebRemarq = (() => {
753
839
  }
754
840
 
755
841
  .remarq-marker:hover { transform: scale(1.2); }
756
- .remarq-marker[data-status="pending"] { background: var(--remarq-pending); }
757
- .remarq-marker[data-status="resolved"] { background: var(--remarq-resolved); opacity: 0.7; }
842
+ .remarq-marker--pending { background: var(--remarq-status-pending); }
843
+ .remarq-marker--in-progress { background: var(--remarq-status-in-progress); }
844
+ .remarq-marker--fixed-unverified { background: var(--remarq-status-fixed-unverified); }
845
+ .remarq-marker--verified { background: var(--remarq-status-verified); opacity: 0.7; }
846
+ .remarq-marker--dismissed { background: var(--remarq-status-dismissed); opacity: 0.5; }
758
847
 
759
848
  .remarq-popup {
760
849
  position: absolute;
@@ -799,14 +888,30 @@ var WebRemarq = (() => {
799
888
 
800
889
  .remarq-popup-actions {
801
890
  display: flex;
802
- justify-content: flex-end;
803
- gap: 8px;
891
+ flex-direction: column;
892
+ gap: 6px;
804
893
  padding: 8px 12px;
805
894
  border-top: 1px solid var(--remarq-border);
806
895
  }
807
896
 
897
+ .remarq-popup-actions-row {
898
+ display: flex;
899
+ flex-wrap: wrap;
900
+ gap: 6px;
901
+ }
902
+
903
+ .remarq-popup-actions-row--transitions {
904
+ justify-content: flex-start;
905
+ }
906
+
907
+ .remarq-popup-actions-row--utility {
908
+ justify-content: flex-end;
909
+ padding-top: 6px;
910
+ border-top: 1px dashed var(--remarq-border);
911
+ }
912
+
808
913
  .remarq-popup-actions button {
809
- padding: 4px 12px;
914
+ padding: 5px 12px;
810
915
  border: 1px solid var(--remarq-border);
811
916
  border-radius: 4px;
812
917
  background: var(--remarq-bg);
@@ -821,6 +926,19 @@ var WebRemarq = (() => {
821
926
  color: #ffffff;
822
927
  }
823
928
 
929
+ .remarq-popup-utility-btn {
930
+ font-size: 11px !important;
931
+ padding: 3px 10px !important;
932
+ color: var(--remarq-text-secondary) !important;
933
+ background: transparent !important;
934
+ border-color: transparent !important;
935
+ }
936
+
937
+ .remarq-popup-utility-btn:hover {
938
+ background: var(--remarq-bg-secondary) !important;
939
+ color: var(--remarq-text) !important;
940
+ }
941
+
824
942
  .remarq-detached-panel {
825
943
  position: fixed;
826
944
  z-index: 2147483646;
@@ -1079,6 +1197,51 @@ var WebRemarq = (() => {
1079
1197
  color: var(--remarq-text-secondary);
1080
1198
  margin-top: 4px;
1081
1199
  }
1200
+
1201
+ .remarq-popup-history {
1202
+ margin-top: 8px;
1203
+ font-size: 12px;
1204
+ color: var(--remarq-text-secondary);
1205
+ }
1206
+
1207
+ .remarq-popup-history summary {
1208
+ cursor: pointer;
1209
+ padding: 4px 0;
1210
+ user-select: none;
1211
+ }
1212
+
1213
+ .remarq-popup-history-list {
1214
+ list-style: none;
1215
+ margin: 4px 0 0 0;
1216
+ padding: 0;
1217
+ }
1218
+
1219
+ .remarq-popup-history-list li {
1220
+ padding: 2px 0;
1221
+ font-size: 11px;
1222
+ line-height: 1.4;
1223
+ }
1224
+
1225
+ .remarq-popup-reason {
1226
+ width: 100%;
1227
+ min-height: 50px;
1228
+ margin-bottom: 8px;
1229
+ padding: 6px;
1230
+ border: 1px solid var(--remarq-border);
1231
+ border-radius: 4px;
1232
+ background: var(--remarq-bg-secondary);
1233
+ color: var(--remarq-text);
1234
+ font-family: inherit;
1235
+ font-size: 12px;
1236
+ resize: vertical;
1237
+ box-sizing: border-box;
1238
+ }
1239
+
1240
+ .remarq-popup-reason-row {
1241
+ display: flex;
1242
+ justify-content: flex-end;
1243
+ gap: 8px;
1244
+ }
1082
1245
  `;
1083
1246
  function injectStyles() {
1084
1247
  if (document.querySelector(`style[${STYLES_ID}]`)) return;
@@ -1186,8 +1349,19 @@ var WebRemarq = (() => {
1186
1349
  this.inspectBtn = this.createButton("inspect", ICONS.inspect, () => callbacks.onInspect());
1187
1350
  this.badgeEl = document.createElement("span");
1188
1351
  this.badgeEl.className = "remarq-badge";
1352
+ this.badgeEl.title = "Needs attention";
1189
1353
  this.badgeEl.style.display = "none";
1190
1354
  this.inspectBtn.appendChild(this.badgeEl);
1355
+ this.verificationBadgeEl = document.createElement("span");
1356
+ this.verificationBadgeEl.className = "remarq-badge remarq-toolbar-badge--verification";
1357
+ this.verificationBadgeEl.title = "Pending your verification";
1358
+ this.verificationBadgeEl.style.display = "none";
1359
+ this.verificationBadgeEl.addEventListener("click", (e) => {
1360
+ var _a3;
1361
+ e.stopPropagation();
1362
+ (_a3 = callbacks.onVerificationBadgeClick) == null ? void 0 : _a3.call(callbacks);
1363
+ });
1364
+ this.inspectBtn.appendChild(this.verificationBadgeEl);
1191
1365
  this.spacingBtn = this.createButton("spacing", ICONS.spacing, () => callbacks.onSpacingToggle());
1192
1366
  this.spacingBtn.disabled = true;
1193
1367
  const copyBtn = this.createButton("copy", ICONS.copy, () => callbacks.onCopy());
@@ -1234,6 +1408,10 @@ var WebRemarq = (() => {
1234
1408
  this.badgeEl.textContent = String(count);
1235
1409
  this.badgeEl.style.display = count > 0 ? "flex" : "none";
1236
1410
  }
1411
+ setVerificationBadgeCount(count) {
1412
+ this.verificationBadgeEl.textContent = String(count);
1413
+ this.verificationBadgeEl.style.display = count > 0 ? "flex" : "none";
1414
+ }
1237
1415
  getFileInput() {
1238
1416
  return this.fileInput;
1239
1417
  }
@@ -1717,6 +1895,47 @@ var WebRemarq = (() => {
1717
1895
  };
1718
1896
 
1719
1897
  // src/ui/popup.ts
1898
+ var STATUS_LABEL = {
1899
+ pending: "Pending",
1900
+ in_progress: "In progress",
1901
+ fixed_unverified: "Fix claimed",
1902
+ verified: "Verified",
1903
+ dismissed: "Dismissed"
1904
+ };
1905
+ var EVENT_LABEL = {
1906
+ created: "Created",
1907
+ acknowledged: "In progress",
1908
+ fix_claimed: "Fix claimed",
1909
+ verified: "Verified",
1910
+ rejected: "Rejected",
1911
+ dismissed: "Dismissed",
1912
+ reopened: "Reopened",
1913
+ migrated: "Migrated"
1914
+ };
1915
+ function actionsForStatus(status) {
1916
+ switch (status) {
1917
+ case "pending":
1918
+ return [
1919
+ { label: "Acknowledge", action: "acknowledge", primary: true },
1920
+ { label: "Dismiss", action: "dismiss", needsReason: true }
1921
+ ];
1922
+ case "in_progress":
1923
+ return [
1924
+ { label: "Mark verified", action: "verify", primary: true },
1925
+ { label: "Dismiss", action: "dismiss", needsReason: true }
1926
+ ];
1927
+ case "fixed_unverified":
1928
+ return [
1929
+ { label: "Verify", action: "verify", primary: true },
1930
+ { label: "Reject", action: "reject", needsReason: true },
1931
+ { label: "Dismiss", action: "dismiss", needsReason: true }
1932
+ ];
1933
+ case "verified":
1934
+ return [{ label: "Reopen", action: "reopen" }];
1935
+ case "dismissed":
1936
+ return [{ label: "Reopen", action: "reopen" }];
1937
+ }
1938
+ }
1720
1939
  var POPUP_WIDTH = 300;
1721
1940
  var POPUP_MARGIN = 8;
1722
1941
  var Popup = class {
@@ -1725,6 +1944,7 @@ var WebRemarq = (() => {
1725
1944
  this.popupEl = null;
1726
1945
  this.keyHandler = null;
1727
1946
  this.outsideClickHandler = null;
1947
+ this.pendingEditFlush = null;
1728
1948
  }
1729
1949
  show(info, position, onSubmit, onCancel) {
1730
1950
  this.hide();
@@ -1809,7 +2029,7 @@ var WebRemarq = (() => {
1809
2029
  popup2.className = "remarq-popup";
1810
2030
  const header = document.createElement("div");
1811
2031
  header.className = "remarq-popup-header";
1812
- header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${info.status}]`;
2032
+ header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${STATUS_LABEL[info.status]}]`;
1813
2033
  const body = document.createElement("div");
1814
2034
  body.className = "remarq-popup-body";
1815
2035
  const makeCommentEl = () => {
@@ -1821,38 +2041,10 @@ var WebRemarq = (() => {
1821
2041
  return el;
1822
2042
  };
1823
2043
  body.appendChild(makeCommentEl());
2044
+ body.appendChild(this.buildLifecycleViewer(info.lifecycle));
1824
2045
  const actions = document.createElement("div");
1825
2046
  actions.className = "remarq-popup-actions";
1826
- if (info.status === "pending") {
1827
- const resolveBtn = document.createElement("button");
1828
- resolveBtn.className = "remarq-primary";
1829
- resolveBtn.textContent = "Resolve";
1830
- resolveBtn.addEventListener("click", () => {
1831
- this.hide();
1832
- callbacks.onResolve();
1833
- });
1834
- actions.appendChild(resolveBtn);
1835
- }
1836
- const copyBtn = document.createElement("button");
1837
- copyBtn.textContent = "Copy";
1838
- copyBtn.addEventListener("click", () => {
1839
- callbacks.onCopy();
1840
- });
1841
- actions.appendChild(copyBtn);
1842
- const deleteBtn = document.createElement("button");
1843
- deleteBtn.textContent = "Delete";
1844
- deleteBtn.addEventListener("click", () => {
1845
- this.hide();
1846
- callbacks.onDelete();
1847
- });
1848
- actions.appendChild(deleteBtn);
1849
- const closeBtn = document.createElement("button");
1850
- closeBtn.textContent = "Close";
1851
- closeBtn.addEventListener("click", () => {
1852
- this.hide();
1853
- callbacks.onClose();
1854
- });
1855
- actions.appendChild(closeBtn);
2047
+ this.renderActionButtons(actions, info, callbacks);
1856
2048
  popup2.appendChild(header);
1857
2049
  popup2.appendChild(body);
1858
2050
  popup2.appendChild(actions);
@@ -1879,7 +2071,103 @@ var WebRemarq = (() => {
1879
2071
  document.addEventListener("mousedown", this.outsideClickHandler);
1880
2072
  }, 0);
1881
2073
  }
2074
+ buildLifecycleViewer(lifecycle) {
2075
+ var _a3, _b;
2076
+ const details = document.createElement("details");
2077
+ details.className = "remarq-popup-history";
2078
+ const summary = document.createElement("summary");
2079
+ summary.textContent = `History (${lifecycle.length})`;
2080
+ details.appendChild(summary);
2081
+ const list = document.createElement("ul");
2082
+ list.className = "remarq-popup-history-list";
2083
+ for (const ev of lifecycle) {
2084
+ const li = document.createElement("li");
2085
+ const when = new Date(ev.timestamp).toLocaleString();
2086
+ const who = (_a3 = ev.actor) != null ? _a3 : "system";
2087
+ const what = (_b = EVENT_LABEL[ev.type]) != null ? _b : ev.type;
2088
+ let text = `${when} \xB7 ${who} \xB7 ${what}`;
2089
+ if (ev.reason) text += ` \u2014 ${ev.reason}`;
2090
+ li.textContent = text;
2091
+ list.appendChild(li);
2092
+ }
2093
+ details.appendChild(list);
2094
+ return details;
2095
+ }
2096
+ renderActionButtons(container, info, callbacks) {
2097
+ container.replaceChildren();
2098
+ const transitions = document.createElement("div");
2099
+ transitions.className = "remarq-popup-actions-row remarq-popup-actions-row--transitions";
2100
+ for (const def of actionsForStatus(info.status)) {
2101
+ const btn = document.createElement("button");
2102
+ btn.textContent = def.label;
2103
+ if (def.primary) btn.className = "remarq-primary";
2104
+ btn.addEventListener("click", () => {
2105
+ if (def.needsReason) {
2106
+ this.showReasonInput(container, info, callbacks, def);
2107
+ } else {
2108
+ this.hide();
2109
+ callbacks.onTransition(def.action);
2110
+ }
2111
+ });
2112
+ transitions.appendChild(btn);
2113
+ }
2114
+ container.appendChild(transitions);
2115
+ const utility = document.createElement("div");
2116
+ utility.className = "remarq-popup-actions-row remarq-popup-actions-row--utility";
2117
+ const copyBtn = document.createElement("button");
2118
+ copyBtn.className = "remarq-popup-utility-btn";
2119
+ copyBtn.textContent = "Copy";
2120
+ copyBtn.addEventListener("click", () => callbacks.onCopy());
2121
+ utility.appendChild(copyBtn);
2122
+ const deleteBtn = document.createElement("button");
2123
+ deleteBtn.className = "remarq-popup-utility-btn";
2124
+ deleteBtn.textContent = "Delete";
2125
+ deleteBtn.addEventListener("click", () => {
2126
+ this.hide();
2127
+ callbacks.onDelete();
2128
+ });
2129
+ utility.appendChild(deleteBtn);
2130
+ const closeBtn = document.createElement("button");
2131
+ closeBtn.className = "remarq-popup-utility-btn";
2132
+ closeBtn.textContent = "Close";
2133
+ closeBtn.addEventListener("click", () => {
2134
+ this.hide();
2135
+ callbacks.onClose();
2136
+ });
2137
+ utility.appendChild(closeBtn);
2138
+ container.appendChild(utility);
2139
+ }
2140
+ showReasonInput(container, info, callbacks, def) {
2141
+ container.replaceChildren();
2142
+ const textarea = document.createElement("textarea");
2143
+ textarea.placeholder = `Reason for ${def.label.toLowerCase()} (optional)\u2026`;
2144
+ textarea.className = "remarq-popup-reason";
2145
+ container.appendChild(textarea);
2146
+ const row = document.createElement("div");
2147
+ row.className = "remarq-popup-reason-row";
2148
+ const cancel = document.createElement("button");
2149
+ cancel.textContent = "Cancel";
2150
+ cancel.addEventListener("click", () => {
2151
+ this.renderActionButtons(container, info, callbacks);
2152
+ });
2153
+ const submit = document.createElement("button");
2154
+ submit.className = "remarq-primary";
2155
+ submit.textContent = "Submit";
2156
+ submit.addEventListener("click", () => {
2157
+ const reason = textarea.value.trim() || void 0;
2158
+ this.hide();
2159
+ callbacks.onTransition(def.action, reason);
2160
+ });
2161
+ row.appendChild(cancel);
2162
+ row.appendChild(submit);
2163
+ container.appendChild(row);
2164
+ textarea.focus();
2165
+ }
1882
2166
  hide() {
2167
+ if (this.pendingEditFlush) {
2168
+ this.pendingEditFlush();
2169
+ this.pendingEditFlush = null;
2170
+ }
1883
2171
  if (this.popupEl) {
1884
2172
  this.popupEl.remove();
1885
2173
  this.popupEl = null;
@@ -1914,12 +2202,8 @@ var WebRemarq = (() => {
1914
2202
  commentEl.replaceWith(textarea);
1915
2203
  textarea.focus();
1916
2204
  textarea.selectionStart = textarea.value.length;
1917
- const saveEdit = () => {
1918
- const newComment = textarea.value.trim();
1919
- if (newComment && newComment !== info.comment) {
1920
- info.comment = newComment;
1921
- callbacks.onEdit(newComment);
1922
- }
2205
+ const restoreView = () => {
2206
+ if (!textarea.isConnected) return;
1923
2207
  const restored = document.createElement("div");
1924
2208
  restored.textContent = info.comment;
1925
2209
  restored.style.cursor = "pointer";
@@ -1927,26 +2211,33 @@ var WebRemarq = (() => {
1927
2211
  restored.addEventListener("click", () => this.enterEditMode(restored, info, callbacks));
1928
2212
  textarea.replaceWith(restored);
1929
2213
  };
2214
+ const commitEdit = () => {
2215
+ this.pendingEditFlush = null;
2216
+ const newComment = textarea.value.trim();
2217
+ if (newComment && newComment !== info.comment) {
2218
+ info.comment = newComment;
2219
+ callbacks.onEdit(newComment);
2220
+ }
2221
+ restoreView();
2222
+ };
2223
+ const cancelEdit = () => {
2224
+ this.pendingEditFlush = null;
2225
+ restoreView();
2226
+ };
2227
+ this.pendingEditFlush = commitEdit;
1930
2228
  textarea.addEventListener("keydown", (e) => {
1931
2229
  if (e.key === "Enter" && !e.shiftKey) {
1932
2230
  e.preventDefault();
1933
- saveEdit();
2231
+ commitEdit();
1934
2232
  }
1935
2233
  if (e.key === "Escape") {
1936
2234
  e.stopPropagation();
1937
- const restored = document.createElement("div");
1938
- restored.textContent = info.comment;
1939
- restored.style.cursor = "pointer";
1940
- restored.title = "Click to edit";
1941
- restored.addEventListener("click", () => this.enterEditMode(restored, info, callbacks));
1942
- textarea.replaceWith(restored);
2235
+ cancelEdit();
1943
2236
  }
1944
2237
  });
1945
2238
  textarea.addEventListener("blur", () => {
1946
2239
  setTimeout(() => {
1947
- if (textarea.isConnected) {
1948
- saveEdit();
1949
- }
2240
+ if (textarea.isConnected) commitEdit();
1950
2241
  }, 50);
1951
2242
  });
1952
2243
  }
@@ -1974,6 +2265,16 @@ var WebRemarq = (() => {
1974
2265
  };
1975
2266
 
1976
2267
  // src/ui/markers.ts
2268
+ var STATUS_COLOR = {
2269
+ pending: "var(--remarq-status-pending)",
2270
+ in_progress: "var(--remarq-status-in-progress)",
2271
+ fixed_unverified: "var(--remarq-status-fixed-unverified)",
2272
+ verified: "var(--remarq-status-verified)",
2273
+ dismissed: "var(--remarq-status-dismissed)"
2274
+ };
2275
+ function statusClass(status) {
2276
+ return `remarq-marker--${status.replace("_", "-")}`;
2277
+ }
1977
2278
  var MarkerManager = class {
1978
2279
  constructor(container, onClick) {
1979
2280
  this.container = container;
@@ -1986,7 +2287,7 @@ var WebRemarq = (() => {
1986
2287
  addMarker(annotation, target) {
1987
2288
  this.counter++;
1988
2289
  const markerEl = document.createElement("div");
1989
- markerEl.className = "remarq-marker";
2290
+ markerEl.className = `remarq-marker ${statusClass(annotation.status)}`;
1990
2291
  markerEl.setAttribute("data-status", annotation.status);
1991
2292
  markerEl.setAttribute("data-annotation-id", annotation.id);
1992
2293
  markerEl.textContent = String(this.counter);
@@ -2012,7 +2313,17 @@ var WebRemarq = (() => {
2012
2313
  const entry = this.markers.get(id);
2013
2314
  if (entry) {
2014
2315
  entry.annotation.status = status;
2316
+ entry.markerEl.className = `remarq-marker ${statusClass(status)}`;
2015
2317
  entry.markerEl.setAttribute("data-status", status);
2318
+ this.applyOutline(entry.target, status);
2319
+ }
2320
+ }
2321
+ scrollToMarker(id) {
2322
+ const entry = this.markers.get(id);
2323
+ if (!entry) return;
2324
+ try {
2325
+ entry.target.scrollIntoView({ behavior: "smooth", block: "center" });
2326
+ } catch (e) {
2016
2327
  }
2017
2328
  }
2018
2329
  clear() {
@@ -2031,7 +2342,7 @@ var WebRemarq = (() => {
2031
2342
  this.clear();
2032
2343
  }
2033
2344
  applyOutline(target, status) {
2034
- const color = status === "pending" ? "#f97316" : "rgba(34, 197, 94, 0.5)";
2345
+ const color = STATUS_COLOR[status];
2035
2346
  target.style.outline = `2px solid ${color}`;
2036
2347
  target.style.outlineOffset = "2px";
2037
2348
  }
@@ -2378,8 +2689,18 @@ var WebRemarq = (() => {
2378
2689
  markers.addMarker(ann, el);
2379
2690
  }
2380
2691
  detachedPanel.update(otherBreakpoint, detached);
2381
- const pendingCount = anns.filter((a) => a.status === "pending").length;
2382
- toolbar.setBadgeCount(pendingCount);
2692
+ const needsAttention = anns.filter(
2693
+ (a) => a.status === "pending" || a.status === "in_progress"
2694
+ ).length;
2695
+ const needsVerification = anns.filter((a) => a.status === "fixed_unverified").length;
2696
+ toolbar.setBadgeCount(needsAttention);
2697
+ toolbar.setVerificationBadgeCount(needsVerification);
2698
+ }
2699
+ function jumpToFirstUnverified() {
2700
+ if (!storage || !markers) return;
2701
+ const ann = storage.getByRoute(currentRoute()).find((a) => a.status === "fixed_unverified");
2702
+ if (!ann) return;
2703
+ markers.scrollToMarker(ann.id);
2383
2704
  }
2384
2705
  function scheduleRefresh() {
2385
2706
  if (refreshScheduled) return;
@@ -2412,6 +2733,7 @@ var WebRemarq = (() => {
2412
2733
  classFilter: options.classFilter,
2413
2734
  dataAttribute: options.dataAttribute
2414
2735
  });
2736
+ const now = Date.now();
2415
2737
  const ann = {
2416
2738
  id: generateId(),
2417
2739
  comment,
@@ -2419,8 +2741,9 @@ var WebRemarq = (() => {
2419
2741
  route: currentRoute(),
2420
2742
  viewport: `${window.innerWidth}x${window.innerHeight}`,
2421
2743
  viewportBucket: toBucket(window.innerWidth),
2422
- timestamp: Date.now(),
2423
- status: "pending"
2744
+ timestamp: now,
2745
+ status: "pending",
2746
+ lifecycle: [{ type: "created", actor: "designer", timestamp: now }]
2424
2747
  };
2425
2748
  cacheElement(ann.id, target);
2426
2749
  storage.add(ann);
@@ -2512,7 +2835,8 @@ var WebRemarq = (() => {
2512
2835
  tag: ann.fingerprint.tagName,
2513
2836
  text: (_a3 = ann.fingerprint.textContent) != null ? _a3 : "",
2514
2837
  comment: ann.comment,
2515
- status: ann.status
2838
+ status: ann.status,
2839
+ lifecycle: ann.lifecycle
2516
2840
  },
2517
2841
  {
2518
2842
  top: window.scrollY + rect.bottom + 8,
@@ -2520,9 +2844,8 @@ var WebRemarq = (() => {
2520
2844
  anchorBottom: window.scrollY + rect.top - 8
2521
2845
  },
2522
2846
  {
2523
- onResolve: () => {
2524
- storage.update(ann.id, { status: "resolved" });
2525
- refreshMarkers();
2847
+ onTransition: (action, reason) => {
2848
+ applyTransition(ann.id, action, reason ? { reason } : void 0);
2526
2849
  },
2527
2850
  onDelete: () => {
2528
2851
  elementCache.delete(ann.id);
@@ -2536,12 +2859,14 @@ var WebRemarq = (() => {
2536
2859
  refreshMarkers();
2537
2860
  },
2538
2861
  onCopy: () => {
2539
- const fp = ann.fingerprint;
2862
+ var _a4;
2863
+ const fresh = (_a4 = storage.getById(ann.id)) != null ? _a4 : ann;
2864
+ const fp = fresh.fingerprint;
2540
2865
  const lines = [
2541
- `[${ann.status}] "${ann.comment}"`,
2866
+ `[${fresh.status}] "${fresh.comment}"`,
2542
2867
  `Element: <${fp.tagName}>${fp.textContent ? ` "${fp.textContent}"` : ""}`,
2543
- `Route: ${ann.route}`,
2544
- `Viewport: ${ann.viewportBucket}px`
2868
+ `Route: ${fresh.route}`,
2869
+ `Viewport: ${fresh.viewportBucket}px`
2545
2870
  ];
2546
2871
  if (fp.sourceLocation) lines.push(`Source: ${fp.sourceLocation}`);
2547
2872
  navigator.clipboard.writeText(lines.join("\n")).then(() => {
@@ -2676,6 +3001,16 @@ var WebRemarq = (() => {
2676
3001
  console.warn("[web-remarq] Clipboard write failed");
2677
3002
  });
2678
3003
  }
3004
+ function applyTransition(id, action, opts = {}) {
3005
+ if (!storage) return;
3006
+ const ann = storage.getById(id);
3007
+ if (!ann) return;
3008
+ const { status, event } = transition(ann, action, opts);
3009
+ const lifecycle = [...ann.lifecycle, event];
3010
+ storage.update(id, { status, lifecycle });
3011
+ markers == null ? void 0 : markers.updateStatus(id, status);
3012
+ refreshMarkers();
3013
+ }
2679
3014
  function setupMutationObserver() {
2680
3015
  mutationObserver = new MutationObserver((mutations) => {
2681
3016
  let hasExternalMutation = false;
@@ -2697,6 +3032,10 @@ var WebRemarq = (() => {
2697
3032
  init(opts) {
2698
3033
  var _a3, _b;
2699
3034
  if (initialized) return;
3035
+ if (!document.body) {
3036
+ document.addEventListener("DOMContentLoaded", () => WebRemarq.init(opts), { once: true });
3037
+ return;
3038
+ }
2700
3039
  options = opts != null ? opts : {};
2701
3040
  try {
2702
3041
  injectStyles();
@@ -2737,7 +3076,8 @@ var WebRemarq = (() => {
2737
3076
  showToast(themeManager.container, "All annotations cleared");
2738
3077
  },
2739
3078
  onThemeToggle: () => themeManager.toggle(),
2740
- onHelp: () => showShortcutsModal(themeManager.container)
3079
+ onHelp: () => showShortcutsModal(themeManager.container),
3080
+ onVerificationBadgeClick: jumpToFirstUnverified
2741
3081
  }, position);
2742
3082
  routeObserver = new RouteObserver();
2743
3083
  unsubRoute = routeObserver.onChange(() => refreshMarkers());
@@ -2831,8 +3171,32 @@ var WebRemarq = (() => {
2831
3171
  elementCache.clear();
2832
3172
  storage == null ? void 0 : storage.clearAll();
2833
3173
  if (initialized) refreshMarkers();
3174
+ },
3175
+ acknowledge(id, opts) {
3176
+ applyTransition(id, "acknowledge", opts);
3177
+ },
3178
+ claimFix(id, opts) {
3179
+ applyTransition(id, "claimFix", opts);
3180
+ },
3181
+ verify(id, opts) {
3182
+ applyTransition(id, "verify", opts);
3183
+ },
3184
+ reject(id, opts) {
3185
+ applyTransition(id, "reject", opts);
3186
+ },
3187
+ dismiss(id, opts) {
3188
+ applyTransition(id, "dismiss", opts);
3189
+ },
3190
+ reopen(id, opts) {
3191
+ applyTransition(id, "reopen", opts);
3192
+ },
3193
+ /** @deprecated Use verify() instead. */
3194
+ markResolved(id) {
3195
+ applyTransition(id, "verify");
2834
3196
  }
2835
3197
  };
2836
- return __toCommonJS(src_exports);
3198
+
3199
+ // src/global.ts
3200
+ globalThis.WebRemarq = WebRemarq;
2837
3201
  })();
2838
- //# sourceMappingURL=web-remarq.global.global.js.map
3202
+ //# sourceMappingURL=web-remarq.global.js.map