web-remarq 0.6.0 → 0.7.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.
package/dist/index.js CHANGED
@@ -65,6 +65,21 @@ function destroyViewportListener() {
65
65
  }
66
66
 
67
67
  // src/core/storage.ts
68
+ function migrateAnnotation(legacy) {
69
+ const rawStatus = legacy.status;
70
+ const status = rawStatus === "resolved" ? "verified" : rawStatus;
71
+ if (Array.isArray(legacy.lifecycle) && legacy.lifecycle.length > 0) {
72
+ return __spreadProps(__spreadValues({}, legacy), { status, lifecycle: legacy.lifecycle });
73
+ }
74
+ const createdTs = typeof legacy.timestamp === "number" ? legacy.timestamp : Date.now();
75
+ const lifecycle = [
76
+ { type: "created", actor: "designer", timestamp: createdTs }
77
+ ];
78
+ if (rawStatus === "resolved") {
79
+ lifecycle.push({ type: "migrated", actor: null, timestamp: Date.now() });
80
+ }
81
+ return __spreadProps(__spreadValues({}, legacy), { status, lifecycle });
82
+ }
68
83
  var AnnotationStorage = class {
69
84
  constructor(adapter) {
70
85
  this.adapter = adapter;
@@ -81,6 +96,9 @@ var AnnotationStorage = class {
81
96
  getByRoute(route) {
82
97
  return this.cache.filter((a) => a.route === route);
83
98
  }
99
+ getById(id) {
100
+ return this.cache.find((a) => a.id === id);
101
+ }
84
102
  async add(annotation) {
85
103
  this.cache.push(annotation);
86
104
  await this.adapter.save(annotation);
@@ -107,7 +125,7 @@ var AnnotationStorage = class {
107
125
  };
108
126
  }
109
127
  async importJSON(data) {
110
- this.cache = [...data.annotations];
128
+ this.cache = data.annotations.map(migrateAnnotation);
111
129
  this.migrateViewportBuckets();
112
130
  await this.adapter.clear();
113
131
  for (const ann of this.cache) {
@@ -117,7 +135,7 @@ var AnnotationStorage = class {
117
135
  async init() {
118
136
  const data = await this.adapter.load();
119
137
  if (data) {
120
- this.cache = data.annotations;
138
+ this.cache = data.annotations.map(migrateAnnotation);
121
139
  this.migrateViewportBuckets();
122
140
  }
123
141
  }
@@ -587,7 +605,16 @@ function generateAgentExport(annotations, viewportBucket) {
587
605
  status: ann.status,
588
606
  timestamp: ann.timestamp,
589
607
  source: resolveSource(ann.fingerprint),
590
- searchHints: buildSearchHints(ann.fingerprint)
608
+ searchHints: buildSearchHints(ann.fingerprint),
609
+ lifecycle: ann.lifecycle.map((ev) => {
610
+ const out = {
611
+ type: ev.type,
612
+ actor: ev.actor,
613
+ timestamp: ev.timestamp
614
+ };
615
+ if (ev.reason !== void 0) out.reason = ev.reason;
616
+ return out;
617
+ })
591
618
  }));
592
619
  return {
593
620
  version: 1,
@@ -597,6 +624,67 @@ function generateAgentExport(annotations, viewportBucket) {
597
624
  };
598
625
  }
599
626
 
627
+ // src/core/lifecycle.ts
628
+ var InvalidTransitionError = class extends Error {
629
+ constructor(from, action) {
630
+ super(`Cannot ${action} from status "${from}"`);
631
+ this.name = "InvalidTransitionError";
632
+ }
633
+ };
634
+ var ACTION_TO_EVENT = {
635
+ acknowledge: "acknowledged",
636
+ claimFix: "fix_claimed",
637
+ verify: "verified",
638
+ reject: "rejected",
639
+ dismiss: "dismissed",
640
+ reopen: "reopened"
641
+ };
642
+ var DEFAULT_ACTOR_BY_EVENT = {
643
+ created: "designer",
644
+ acknowledged: "developer",
645
+ fix_claimed: "agent",
646
+ verified: "developer",
647
+ rejected: "developer",
648
+ dismissed: "developer",
649
+ reopened: "developer",
650
+ migrated: null
651
+ };
652
+ function createEvent(type, opts = {}) {
653
+ var _a3, _b;
654
+ const event = {
655
+ type,
656
+ actor: (_a3 = opts.actor) != null ? _a3 : DEFAULT_ACTOR_BY_EVENT[type],
657
+ timestamp: (_b = opts.timestamp) != null ? _b : Date.now()
658
+ };
659
+ if (opts.actorName !== void 0) event.actorName = opts.actorName;
660
+ if (opts.reason !== void 0) event.reason = opts.reason;
661
+ return event;
662
+ }
663
+ function nextStatus(from, action) {
664
+ switch (action) {
665
+ case "acknowledge":
666
+ return from === "pending" ? "in_progress" : null;
667
+ case "claimFix":
668
+ return from === "pending" || from === "in_progress" ? "fixed_unverified" : null;
669
+ case "verify":
670
+ return from === "fixed_unverified" || from === "in_progress" ? "verified" : null;
671
+ case "reject":
672
+ return from === "fixed_unverified" ? "pending" : null;
673
+ case "dismiss":
674
+ return from === "pending" || from === "in_progress" || from === "fixed_unverified" ? "dismissed" : null;
675
+ case "reopen":
676
+ return from === "dismissed" || from === "verified" ? "pending" : null;
677
+ }
678
+ }
679
+ function transition(annotation, action, opts = {}) {
680
+ const next = nextStatus(annotation.status, action);
681
+ if (next === null) {
682
+ throw new InvalidTransitionError(annotation.status, action);
683
+ }
684
+ const event = createEvent(ACTION_TO_EVENT[action], opts);
685
+ return { status: next, event };
686
+ }
687
+
600
688
  // src/ui/styles.ts
601
689
  var STYLES_ID = "data-remarq-styles";
602
690
  var CSS = `
@@ -611,6 +699,11 @@ var CSS = `
611
699
  --remarq-resolved: #22c55e;
612
700
  --remarq-overlay: rgba(59, 130, 246, 0.15);
613
701
  --remarq-shadow: 0 4px 12px rgba(0,0,0,0.15);
702
+ --remarq-status-pending: #f97316;
703
+ --remarq-status-in-progress: #eab308;
704
+ --remarq-status-fixed-unverified: #3b82f6;
705
+ --remarq-status-verified: #22c55e;
706
+ --remarq-status-dismissed: #6b7280;
614
707
  }
615
708
 
616
709
  [data-remarq-theme="dark"] {
@@ -624,6 +717,11 @@ var CSS = `
624
717
  --remarq-resolved: #4ade80;
625
718
  --remarq-overlay: rgba(96, 165, 250, 0.15);
626
719
  --remarq-shadow: 0 4px 12px rgba(0,0,0,0.4);
720
+ --remarq-status-pending: #fb923c;
721
+ --remarq-status-in-progress: #facc15;
722
+ --remarq-status-fixed-unverified: #60a5fa;
723
+ --remarq-status-verified: #4ade80;
724
+ --remarq-status-dismissed: #9ca3af;
627
725
  }
628
726
 
629
727
  .remarq-toolbar {
@@ -667,19 +765,29 @@ var CSS = `
667
765
 
668
766
  .remarq-badge {
669
767
  position: absolute;
670
- top: -4px;
671
- right: -4px;
672
- min-width: 16px;
673
- height: 16px;
674
- padding: 0 4px;
675
- border-radius: 8px;
768
+ top: -6px;
769
+ right: -6px;
770
+ width: 18px;
771
+ height: 18px;
772
+ padding: 0;
773
+ border-radius: 50%;
676
774
  background: var(--remarq-pending);
677
775
  color: #ffffff;
678
776
  font-size: 10px;
679
777
  font-weight: 600;
778
+ line-height: 1;
680
779
  display: flex;
681
780
  align-items: center;
682
781
  justify-content: center;
782
+ box-sizing: border-box;
783
+ }
784
+
785
+ .remarq-toolbar-badge--verification {
786
+ top: auto;
787
+ bottom: -6px;
788
+ right: -6px;
789
+ background: var(--remarq-status-fixed-unverified);
790
+ cursor: pointer;
683
791
  }
684
792
 
685
793
  .remarq-overlay {
@@ -729,8 +837,11 @@ var CSS = `
729
837
  }
730
838
 
731
839
  .remarq-marker:hover { transform: scale(1.2); }
732
- .remarq-marker[data-status="pending"] { background: var(--remarq-pending); }
733
- .remarq-marker[data-status="resolved"] { background: var(--remarq-resolved); opacity: 0.7; }
840
+ .remarq-marker--pending { background: var(--remarq-status-pending); }
841
+ .remarq-marker--in-progress { background: var(--remarq-status-in-progress); }
842
+ .remarq-marker--fixed-unverified { background: var(--remarq-status-fixed-unverified); }
843
+ .remarq-marker--verified { background: var(--remarq-status-verified); opacity: 0.7; }
844
+ .remarq-marker--dismissed { background: var(--remarq-status-dismissed); opacity: 0.5; }
734
845
 
735
846
  .remarq-popup {
736
847
  position: absolute;
@@ -775,14 +886,30 @@ var CSS = `
775
886
 
776
887
  .remarq-popup-actions {
777
888
  display: flex;
778
- justify-content: flex-end;
779
- gap: 8px;
889
+ flex-direction: column;
890
+ gap: 6px;
780
891
  padding: 8px 12px;
781
892
  border-top: 1px solid var(--remarq-border);
782
893
  }
783
894
 
895
+ .remarq-popup-actions-row {
896
+ display: flex;
897
+ flex-wrap: wrap;
898
+ gap: 6px;
899
+ }
900
+
901
+ .remarq-popup-actions-row--transitions {
902
+ justify-content: flex-start;
903
+ }
904
+
905
+ .remarq-popup-actions-row--utility {
906
+ justify-content: flex-end;
907
+ padding-top: 6px;
908
+ border-top: 1px dashed var(--remarq-border);
909
+ }
910
+
784
911
  .remarq-popup-actions button {
785
- padding: 4px 12px;
912
+ padding: 5px 12px;
786
913
  border: 1px solid var(--remarq-border);
787
914
  border-radius: 4px;
788
915
  background: var(--remarq-bg);
@@ -797,6 +924,19 @@ var CSS = `
797
924
  color: #ffffff;
798
925
  }
799
926
 
927
+ .remarq-popup-utility-btn {
928
+ font-size: 11px !important;
929
+ padding: 3px 10px !important;
930
+ color: var(--remarq-text-secondary) !important;
931
+ background: transparent !important;
932
+ border-color: transparent !important;
933
+ }
934
+
935
+ .remarq-popup-utility-btn:hover {
936
+ background: var(--remarq-bg-secondary) !important;
937
+ color: var(--remarq-text) !important;
938
+ }
939
+
800
940
  .remarq-detached-panel {
801
941
  position: fixed;
802
942
  z-index: 2147483646;
@@ -1055,6 +1195,51 @@ var CSS = `
1055
1195
  color: var(--remarq-text-secondary);
1056
1196
  margin-top: 4px;
1057
1197
  }
1198
+
1199
+ .remarq-popup-history {
1200
+ margin-top: 8px;
1201
+ font-size: 12px;
1202
+ color: var(--remarq-text-secondary);
1203
+ }
1204
+
1205
+ .remarq-popup-history summary {
1206
+ cursor: pointer;
1207
+ padding: 4px 0;
1208
+ user-select: none;
1209
+ }
1210
+
1211
+ .remarq-popup-history-list {
1212
+ list-style: none;
1213
+ margin: 4px 0 0 0;
1214
+ padding: 0;
1215
+ }
1216
+
1217
+ .remarq-popup-history-list li {
1218
+ padding: 2px 0;
1219
+ font-size: 11px;
1220
+ line-height: 1.4;
1221
+ }
1222
+
1223
+ .remarq-popup-reason {
1224
+ width: 100%;
1225
+ min-height: 50px;
1226
+ margin-bottom: 8px;
1227
+ padding: 6px;
1228
+ border: 1px solid var(--remarq-border);
1229
+ border-radius: 4px;
1230
+ background: var(--remarq-bg-secondary);
1231
+ color: var(--remarq-text);
1232
+ font-family: inherit;
1233
+ font-size: 12px;
1234
+ resize: vertical;
1235
+ box-sizing: border-box;
1236
+ }
1237
+
1238
+ .remarq-popup-reason-row {
1239
+ display: flex;
1240
+ justify-content: flex-end;
1241
+ gap: 8px;
1242
+ }
1058
1243
  `;
1059
1244
  function injectStyles() {
1060
1245
  if (document.querySelector(`style[${STYLES_ID}]`)) return;
@@ -1162,8 +1347,19 @@ var Toolbar = class {
1162
1347
  this.inspectBtn = this.createButton("inspect", ICONS.inspect, () => callbacks.onInspect());
1163
1348
  this.badgeEl = document.createElement("span");
1164
1349
  this.badgeEl.className = "remarq-badge";
1350
+ this.badgeEl.title = "Needs attention";
1165
1351
  this.badgeEl.style.display = "none";
1166
1352
  this.inspectBtn.appendChild(this.badgeEl);
1353
+ this.verificationBadgeEl = document.createElement("span");
1354
+ this.verificationBadgeEl.className = "remarq-badge remarq-toolbar-badge--verification";
1355
+ this.verificationBadgeEl.title = "Pending your verification";
1356
+ this.verificationBadgeEl.style.display = "none";
1357
+ this.verificationBadgeEl.addEventListener("click", (e) => {
1358
+ var _a3;
1359
+ e.stopPropagation();
1360
+ (_a3 = callbacks.onVerificationBadgeClick) == null ? void 0 : _a3.call(callbacks);
1361
+ });
1362
+ this.inspectBtn.appendChild(this.verificationBadgeEl);
1167
1363
  this.spacingBtn = this.createButton("spacing", ICONS.spacing, () => callbacks.onSpacingToggle());
1168
1364
  this.spacingBtn.disabled = true;
1169
1365
  const copyBtn = this.createButton("copy", ICONS.copy, () => callbacks.onCopy());
@@ -1210,6 +1406,10 @@ var Toolbar = class {
1210
1406
  this.badgeEl.textContent = String(count);
1211
1407
  this.badgeEl.style.display = count > 0 ? "flex" : "none";
1212
1408
  }
1409
+ setVerificationBadgeCount(count) {
1410
+ this.verificationBadgeEl.textContent = String(count);
1411
+ this.verificationBadgeEl.style.display = count > 0 ? "flex" : "none";
1412
+ }
1213
1413
  getFileInput() {
1214
1414
  return this.fileInput;
1215
1415
  }
@@ -1693,6 +1893,47 @@ var SpacingOverlay = class {
1693
1893
  };
1694
1894
 
1695
1895
  // src/ui/popup.ts
1896
+ var STATUS_LABEL = {
1897
+ pending: "Pending",
1898
+ in_progress: "In progress",
1899
+ fixed_unverified: "Fix claimed",
1900
+ verified: "Verified",
1901
+ dismissed: "Dismissed"
1902
+ };
1903
+ var EVENT_LABEL = {
1904
+ created: "Created",
1905
+ acknowledged: "In progress",
1906
+ fix_claimed: "Fix claimed",
1907
+ verified: "Verified",
1908
+ rejected: "Rejected",
1909
+ dismissed: "Dismissed",
1910
+ reopened: "Reopened",
1911
+ migrated: "Migrated"
1912
+ };
1913
+ function actionsForStatus(status) {
1914
+ switch (status) {
1915
+ case "pending":
1916
+ return [
1917
+ { label: "Acknowledge", action: "acknowledge", primary: true },
1918
+ { label: "Dismiss", action: "dismiss", needsReason: true }
1919
+ ];
1920
+ case "in_progress":
1921
+ return [
1922
+ { label: "Mark verified", action: "verify", primary: true },
1923
+ { label: "Dismiss", action: "dismiss", needsReason: true }
1924
+ ];
1925
+ case "fixed_unverified":
1926
+ return [
1927
+ { label: "Verify", action: "verify", primary: true },
1928
+ { label: "Reject", action: "reject", needsReason: true },
1929
+ { label: "Dismiss", action: "dismiss", needsReason: true }
1930
+ ];
1931
+ case "verified":
1932
+ return [{ label: "Reopen", action: "reopen" }];
1933
+ case "dismissed":
1934
+ return [{ label: "Reopen", action: "reopen" }];
1935
+ }
1936
+ }
1696
1937
  var POPUP_WIDTH = 300;
1697
1938
  var POPUP_MARGIN = 8;
1698
1939
  var Popup = class {
@@ -1701,6 +1942,7 @@ var Popup = class {
1701
1942
  this.popupEl = null;
1702
1943
  this.keyHandler = null;
1703
1944
  this.outsideClickHandler = null;
1945
+ this.pendingEditFlush = null;
1704
1946
  }
1705
1947
  show(info, position, onSubmit, onCancel) {
1706
1948
  this.hide();
@@ -1785,7 +2027,7 @@ var Popup = class {
1785
2027
  popup2.className = "remarq-popup";
1786
2028
  const header = document.createElement("div");
1787
2029
  header.className = "remarq-popup-header";
1788
- header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${info.status}]`;
2030
+ header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${STATUS_LABEL[info.status]}]`;
1789
2031
  const body = document.createElement("div");
1790
2032
  body.className = "remarq-popup-body";
1791
2033
  const makeCommentEl = () => {
@@ -1797,38 +2039,10 @@ var Popup = class {
1797
2039
  return el;
1798
2040
  };
1799
2041
  body.appendChild(makeCommentEl());
2042
+ body.appendChild(this.buildLifecycleViewer(info.lifecycle));
1800
2043
  const actions = document.createElement("div");
1801
2044
  actions.className = "remarq-popup-actions";
1802
- if (info.status === "pending") {
1803
- const resolveBtn = document.createElement("button");
1804
- resolveBtn.className = "remarq-primary";
1805
- resolveBtn.textContent = "Resolve";
1806
- resolveBtn.addEventListener("click", () => {
1807
- this.hide();
1808
- callbacks.onResolve();
1809
- });
1810
- actions.appendChild(resolveBtn);
1811
- }
1812
- const copyBtn = document.createElement("button");
1813
- copyBtn.textContent = "Copy";
1814
- copyBtn.addEventListener("click", () => {
1815
- callbacks.onCopy();
1816
- });
1817
- actions.appendChild(copyBtn);
1818
- const deleteBtn = document.createElement("button");
1819
- deleteBtn.textContent = "Delete";
1820
- deleteBtn.addEventListener("click", () => {
1821
- this.hide();
1822
- callbacks.onDelete();
1823
- });
1824
- actions.appendChild(deleteBtn);
1825
- const closeBtn = document.createElement("button");
1826
- closeBtn.textContent = "Close";
1827
- closeBtn.addEventListener("click", () => {
1828
- this.hide();
1829
- callbacks.onClose();
1830
- });
1831
- actions.appendChild(closeBtn);
2045
+ this.renderActionButtons(actions, info, callbacks);
1832
2046
  popup2.appendChild(header);
1833
2047
  popup2.appendChild(body);
1834
2048
  popup2.appendChild(actions);
@@ -1855,7 +2069,103 @@ var Popup = class {
1855
2069
  document.addEventListener("mousedown", this.outsideClickHandler);
1856
2070
  }, 0);
1857
2071
  }
2072
+ buildLifecycleViewer(lifecycle) {
2073
+ var _a3, _b;
2074
+ const details = document.createElement("details");
2075
+ details.className = "remarq-popup-history";
2076
+ const summary = document.createElement("summary");
2077
+ summary.textContent = `History (${lifecycle.length})`;
2078
+ details.appendChild(summary);
2079
+ const list = document.createElement("ul");
2080
+ list.className = "remarq-popup-history-list";
2081
+ for (const ev of lifecycle) {
2082
+ const li = document.createElement("li");
2083
+ const when = new Date(ev.timestamp).toLocaleString();
2084
+ const who = (_a3 = ev.actor) != null ? _a3 : "system";
2085
+ const what = (_b = EVENT_LABEL[ev.type]) != null ? _b : ev.type;
2086
+ let text = `${when} \xB7 ${who} \xB7 ${what}`;
2087
+ if (ev.reason) text += ` \u2014 ${ev.reason}`;
2088
+ li.textContent = text;
2089
+ list.appendChild(li);
2090
+ }
2091
+ details.appendChild(list);
2092
+ return details;
2093
+ }
2094
+ renderActionButtons(container, info, callbacks) {
2095
+ container.replaceChildren();
2096
+ const transitions = document.createElement("div");
2097
+ transitions.className = "remarq-popup-actions-row remarq-popup-actions-row--transitions";
2098
+ for (const def of actionsForStatus(info.status)) {
2099
+ const btn = document.createElement("button");
2100
+ btn.textContent = def.label;
2101
+ if (def.primary) btn.className = "remarq-primary";
2102
+ btn.addEventListener("click", () => {
2103
+ if (def.needsReason) {
2104
+ this.showReasonInput(container, info, callbacks, def);
2105
+ } else {
2106
+ this.hide();
2107
+ callbacks.onTransition(def.action);
2108
+ }
2109
+ });
2110
+ transitions.appendChild(btn);
2111
+ }
2112
+ container.appendChild(transitions);
2113
+ const utility = document.createElement("div");
2114
+ utility.className = "remarq-popup-actions-row remarq-popup-actions-row--utility";
2115
+ const copyBtn = document.createElement("button");
2116
+ copyBtn.className = "remarq-popup-utility-btn";
2117
+ copyBtn.textContent = "Copy";
2118
+ copyBtn.addEventListener("click", () => callbacks.onCopy());
2119
+ utility.appendChild(copyBtn);
2120
+ const deleteBtn = document.createElement("button");
2121
+ deleteBtn.className = "remarq-popup-utility-btn";
2122
+ deleteBtn.textContent = "Delete";
2123
+ deleteBtn.addEventListener("click", () => {
2124
+ this.hide();
2125
+ callbacks.onDelete();
2126
+ });
2127
+ utility.appendChild(deleteBtn);
2128
+ const closeBtn = document.createElement("button");
2129
+ closeBtn.className = "remarq-popup-utility-btn";
2130
+ closeBtn.textContent = "Close";
2131
+ closeBtn.addEventListener("click", () => {
2132
+ this.hide();
2133
+ callbacks.onClose();
2134
+ });
2135
+ utility.appendChild(closeBtn);
2136
+ container.appendChild(utility);
2137
+ }
2138
+ showReasonInput(container, info, callbacks, def) {
2139
+ container.replaceChildren();
2140
+ const textarea = document.createElement("textarea");
2141
+ textarea.placeholder = `Reason for ${def.label.toLowerCase()} (optional)\u2026`;
2142
+ textarea.className = "remarq-popup-reason";
2143
+ container.appendChild(textarea);
2144
+ const row = document.createElement("div");
2145
+ row.className = "remarq-popup-reason-row";
2146
+ const cancel = document.createElement("button");
2147
+ cancel.textContent = "Cancel";
2148
+ cancel.addEventListener("click", () => {
2149
+ this.renderActionButtons(container, info, callbacks);
2150
+ });
2151
+ const submit = document.createElement("button");
2152
+ submit.className = "remarq-primary";
2153
+ submit.textContent = "Submit";
2154
+ submit.addEventListener("click", () => {
2155
+ const reason = textarea.value.trim() || void 0;
2156
+ this.hide();
2157
+ callbacks.onTransition(def.action, reason);
2158
+ });
2159
+ row.appendChild(cancel);
2160
+ row.appendChild(submit);
2161
+ container.appendChild(row);
2162
+ textarea.focus();
2163
+ }
1858
2164
  hide() {
2165
+ if (this.pendingEditFlush) {
2166
+ this.pendingEditFlush();
2167
+ this.pendingEditFlush = null;
2168
+ }
1859
2169
  if (this.popupEl) {
1860
2170
  this.popupEl.remove();
1861
2171
  this.popupEl = null;
@@ -1890,12 +2200,8 @@ var Popup = class {
1890
2200
  commentEl.replaceWith(textarea);
1891
2201
  textarea.focus();
1892
2202
  textarea.selectionStart = textarea.value.length;
1893
- const saveEdit = () => {
1894
- const newComment = textarea.value.trim();
1895
- if (newComment && newComment !== info.comment) {
1896
- info.comment = newComment;
1897
- callbacks.onEdit(newComment);
1898
- }
2203
+ const restoreView = () => {
2204
+ if (!textarea.isConnected) return;
1899
2205
  const restored = document.createElement("div");
1900
2206
  restored.textContent = info.comment;
1901
2207
  restored.style.cursor = "pointer";
@@ -1903,26 +2209,33 @@ var Popup = class {
1903
2209
  restored.addEventListener("click", () => this.enterEditMode(restored, info, callbacks));
1904
2210
  textarea.replaceWith(restored);
1905
2211
  };
2212
+ const commitEdit = () => {
2213
+ this.pendingEditFlush = null;
2214
+ const newComment = textarea.value.trim();
2215
+ if (newComment && newComment !== info.comment) {
2216
+ info.comment = newComment;
2217
+ callbacks.onEdit(newComment);
2218
+ }
2219
+ restoreView();
2220
+ };
2221
+ const cancelEdit = () => {
2222
+ this.pendingEditFlush = null;
2223
+ restoreView();
2224
+ };
2225
+ this.pendingEditFlush = commitEdit;
1906
2226
  textarea.addEventListener("keydown", (e) => {
1907
2227
  if (e.key === "Enter" && !e.shiftKey) {
1908
2228
  e.preventDefault();
1909
- saveEdit();
2229
+ commitEdit();
1910
2230
  }
1911
2231
  if (e.key === "Escape") {
1912
2232
  e.stopPropagation();
1913
- const restored = document.createElement("div");
1914
- restored.textContent = info.comment;
1915
- restored.style.cursor = "pointer";
1916
- restored.title = "Click to edit";
1917
- restored.addEventListener("click", () => this.enterEditMode(restored, info, callbacks));
1918
- textarea.replaceWith(restored);
2233
+ cancelEdit();
1919
2234
  }
1920
2235
  });
1921
2236
  textarea.addEventListener("blur", () => {
1922
2237
  setTimeout(() => {
1923
- if (textarea.isConnected) {
1924
- saveEdit();
1925
- }
2238
+ if (textarea.isConnected) commitEdit();
1926
2239
  }, 50);
1927
2240
  });
1928
2241
  }
@@ -1950,6 +2263,16 @@ var Popup = class {
1950
2263
  };
1951
2264
 
1952
2265
  // src/ui/markers.ts
2266
+ var STATUS_COLOR = {
2267
+ pending: "var(--remarq-status-pending)",
2268
+ in_progress: "var(--remarq-status-in-progress)",
2269
+ fixed_unverified: "var(--remarq-status-fixed-unverified)",
2270
+ verified: "var(--remarq-status-verified)",
2271
+ dismissed: "var(--remarq-status-dismissed)"
2272
+ };
2273
+ function statusClass(status) {
2274
+ return `remarq-marker--${status.replace("_", "-")}`;
2275
+ }
1953
2276
  var MarkerManager = class {
1954
2277
  constructor(container, onClick) {
1955
2278
  this.container = container;
@@ -1962,7 +2285,7 @@ var MarkerManager = class {
1962
2285
  addMarker(annotation, target) {
1963
2286
  this.counter++;
1964
2287
  const markerEl = document.createElement("div");
1965
- markerEl.className = "remarq-marker";
2288
+ markerEl.className = `remarq-marker ${statusClass(annotation.status)}`;
1966
2289
  markerEl.setAttribute("data-status", annotation.status);
1967
2290
  markerEl.setAttribute("data-annotation-id", annotation.id);
1968
2291
  markerEl.textContent = String(this.counter);
@@ -1988,7 +2311,17 @@ var MarkerManager = class {
1988
2311
  const entry = this.markers.get(id);
1989
2312
  if (entry) {
1990
2313
  entry.annotation.status = status;
2314
+ entry.markerEl.className = `remarq-marker ${statusClass(status)}`;
1991
2315
  entry.markerEl.setAttribute("data-status", status);
2316
+ this.applyOutline(entry.target, status);
2317
+ }
2318
+ }
2319
+ scrollToMarker(id) {
2320
+ const entry = this.markers.get(id);
2321
+ if (!entry) return;
2322
+ try {
2323
+ entry.target.scrollIntoView({ behavior: "smooth", block: "center" });
2324
+ } catch (e) {
1992
2325
  }
1993
2326
  }
1994
2327
  clear() {
@@ -2007,7 +2340,7 @@ var MarkerManager = class {
2007
2340
  this.clear();
2008
2341
  }
2009
2342
  applyOutline(target, status) {
2010
- const color = status === "pending" ? "#f97316" : "rgba(34, 197, 94, 0.5)";
2343
+ const color = STATUS_COLOR[status];
2011
2344
  target.style.outline = `2px solid ${color}`;
2012
2345
  target.style.outlineOffset = "2px";
2013
2346
  }
@@ -2354,8 +2687,18 @@ function refreshMarkers() {
2354
2687
  markers.addMarker(ann, el);
2355
2688
  }
2356
2689
  detachedPanel.update(otherBreakpoint, detached);
2357
- const pendingCount = anns.filter((a) => a.status === "pending").length;
2358
- toolbar.setBadgeCount(pendingCount);
2690
+ const needsAttention = anns.filter(
2691
+ (a) => a.status === "pending" || a.status === "in_progress"
2692
+ ).length;
2693
+ const needsVerification = anns.filter((a) => a.status === "fixed_unverified").length;
2694
+ toolbar.setBadgeCount(needsAttention);
2695
+ toolbar.setVerificationBadgeCount(needsVerification);
2696
+ }
2697
+ function jumpToFirstUnverified() {
2698
+ if (!storage || !markers) return;
2699
+ const ann = storage.getByRoute(currentRoute()).find((a) => a.status === "fixed_unverified");
2700
+ if (!ann) return;
2701
+ markers.scrollToMarker(ann.id);
2359
2702
  }
2360
2703
  function scheduleRefresh() {
2361
2704
  if (refreshScheduled) return;
@@ -2388,6 +2731,7 @@ function handleInspectClick(e) {
2388
2731
  classFilter: options.classFilter,
2389
2732
  dataAttribute: options.dataAttribute
2390
2733
  });
2734
+ const now = Date.now();
2391
2735
  const ann = {
2392
2736
  id: generateId(),
2393
2737
  comment,
@@ -2395,8 +2739,9 @@ function handleInspectClick(e) {
2395
2739
  route: currentRoute(),
2396
2740
  viewport: `${window.innerWidth}x${window.innerHeight}`,
2397
2741
  viewportBucket: toBucket(window.innerWidth),
2398
- timestamp: Date.now(),
2399
- status: "pending"
2742
+ timestamp: now,
2743
+ status: "pending",
2744
+ lifecycle: [{ type: "created", actor: "designer", timestamp: now }]
2400
2745
  };
2401
2746
  cacheElement(ann.id, target);
2402
2747
  storage.add(ann);
@@ -2488,7 +2833,8 @@ function handleMarkerClick(annotationId) {
2488
2833
  tag: ann.fingerprint.tagName,
2489
2834
  text: (_a3 = ann.fingerprint.textContent) != null ? _a3 : "",
2490
2835
  comment: ann.comment,
2491
- status: ann.status
2836
+ status: ann.status,
2837
+ lifecycle: ann.lifecycle
2492
2838
  },
2493
2839
  {
2494
2840
  top: window.scrollY + rect.bottom + 8,
@@ -2496,9 +2842,8 @@ function handleMarkerClick(annotationId) {
2496
2842
  anchorBottom: window.scrollY + rect.top - 8
2497
2843
  },
2498
2844
  {
2499
- onResolve: () => {
2500
- storage.update(ann.id, { status: "resolved" });
2501
- refreshMarkers();
2845
+ onTransition: (action, reason) => {
2846
+ applyTransition(ann.id, action, reason ? { reason } : void 0);
2502
2847
  },
2503
2848
  onDelete: () => {
2504
2849
  elementCache.delete(ann.id);
@@ -2512,12 +2857,14 @@ function handleMarkerClick(annotationId) {
2512
2857
  refreshMarkers();
2513
2858
  },
2514
2859
  onCopy: () => {
2515
- const fp = ann.fingerprint;
2860
+ var _a4;
2861
+ const fresh = (_a4 = storage.getById(ann.id)) != null ? _a4 : ann;
2862
+ const fp = fresh.fingerprint;
2516
2863
  const lines = [
2517
- `[${ann.status}] "${ann.comment}"`,
2864
+ `[${fresh.status}] "${fresh.comment}"`,
2518
2865
  `Element: <${fp.tagName}>${fp.textContent ? ` "${fp.textContent}"` : ""}`,
2519
- `Route: ${ann.route}`,
2520
- `Viewport: ${ann.viewportBucket}px`
2866
+ `Route: ${fresh.route}`,
2867
+ `Viewport: ${fresh.viewportBucket}px`
2521
2868
  ];
2522
2869
  if (fp.sourceLocation) lines.push(`Source: ${fp.sourceLocation}`);
2523
2870
  navigator.clipboard.writeText(lines.join("\n")).then(() => {
@@ -2652,6 +2999,16 @@ function copyAgentToClipboard() {
2652
2999
  console.warn("[web-remarq] Clipboard write failed");
2653
3000
  });
2654
3001
  }
3002
+ function applyTransition(id, action, opts = {}) {
3003
+ if (!storage) return;
3004
+ const ann = storage.getById(id);
3005
+ if (!ann) return;
3006
+ const { status, event } = transition(ann, action, opts);
3007
+ const lifecycle = [...ann.lifecycle, event];
3008
+ storage.update(id, { status, lifecycle });
3009
+ markers == null ? void 0 : markers.updateStatus(id, status);
3010
+ refreshMarkers();
3011
+ }
2655
3012
  function setupMutationObserver() {
2656
3013
  mutationObserver = new MutationObserver((mutations) => {
2657
3014
  let hasExternalMutation = false;
@@ -2713,7 +3070,8 @@ var WebRemarq = {
2713
3070
  showToast(themeManager.container, "All annotations cleared");
2714
3071
  },
2715
3072
  onThemeToggle: () => themeManager.toggle(),
2716
- onHelp: () => showShortcutsModal(themeManager.container)
3073
+ onHelp: () => showShortcutsModal(themeManager.container),
3074
+ onVerificationBadgeClick: jumpToFirstUnverified
2717
3075
  }, position);
2718
3076
  routeObserver = new RouteObserver();
2719
3077
  unsubRoute = routeObserver.onChange(() => refreshMarkers());
@@ -2807,6 +3165,28 @@ var WebRemarq = {
2807
3165
  elementCache.clear();
2808
3166
  storage == null ? void 0 : storage.clearAll();
2809
3167
  if (initialized) refreshMarkers();
3168
+ },
3169
+ acknowledge(id, opts) {
3170
+ applyTransition(id, "acknowledge", opts);
3171
+ },
3172
+ claimFix(id, opts) {
3173
+ applyTransition(id, "claimFix", opts);
3174
+ },
3175
+ verify(id, opts) {
3176
+ applyTransition(id, "verify", opts);
3177
+ },
3178
+ reject(id, opts) {
3179
+ applyTransition(id, "reject", opts);
3180
+ },
3181
+ dismiss(id, opts) {
3182
+ applyTransition(id, "dismiss", opts);
3183
+ },
3184
+ reopen(id, opts) {
3185
+ applyTransition(id, "reopen", opts);
3186
+ },
3187
+ /** @deprecated Use verify() instead. */
3188
+ markResolved(id) {
3189
+ applyTransition(id, "verify");
2810
3190
  }
2811
3191
  };
2812
3192
  export {