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.cjs CHANGED
@@ -89,6 +89,21 @@ function destroyViewportListener() {
89
89
  }
90
90
 
91
91
  // src/core/storage.ts
92
+ function migrateAnnotation(legacy) {
93
+ const rawStatus = legacy.status;
94
+ const status = rawStatus === "resolved" ? "verified" : rawStatus;
95
+ if (Array.isArray(legacy.lifecycle) && legacy.lifecycle.length > 0) {
96
+ return __spreadProps(__spreadValues({}, legacy), { status, lifecycle: legacy.lifecycle });
97
+ }
98
+ const createdTs = typeof legacy.timestamp === "number" ? legacy.timestamp : Date.now();
99
+ const lifecycle = [
100
+ { type: "created", actor: "designer", timestamp: createdTs }
101
+ ];
102
+ if (rawStatus === "resolved") {
103
+ lifecycle.push({ type: "migrated", actor: null, timestamp: Date.now() });
104
+ }
105
+ return __spreadProps(__spreadValues({}, legacy), { status, lifecycle });
106
+ }
92
107
  var AnnotationStorage = class {
93
108
  constructor(adapter) {
94
109
  this.adapter = adapter;
@@ -105,6 +120,9 @@ var AnnotationStorage = class {
105
120
  getByRoute(route) {
106
121
  return this.cache.filter((a) => a.route === route);
107
122
  }
123
+ getById(id) {
124
+ return this.cache.find((a) => a.id === id);
125
+ }
108
126
  async add(annotation) {
109
127
  this.cache.push(annotation);
110
128
  await this.adapter.save(annotation);
@@ -131,7 +149,7 @@ var AnnotationStorage = class {
131
149
  };
132
150
  }
133
151
  async importJSON(data) {
134
- this.cache = [...data.annotations];
152
+ this.cache = data.annotations.map(migrateAnnotation);
135
153
  this.migrateViewportBuckets();
136
154
  await this.adapter.clear();
137
155
  for (const ann of this.cache) {
@@ -141,7 +159,7 @@ var AnnotationStorage = class {
141
159
  async init() {
142
160
  const data = await this.adapter.load();
143
161
  if (data) {
144
- this.cache = data.annotations;
162
+ this.cache = data.annotations.map(migrateAnnotation);
145
163
  this.migrateViewportBuckets();
146
164
  }
147
165
  }
@@ -611,7 +629,16 @@ function generateAgentExport(annotations, viewportBucket) {
611
629
  status: ann.status,
612
630
  timestamp: ann.timestamp,
613
631
  source: resolveSource(ann.fingerprint),
614
- searchHints: buildSearchHints(ann.fingerprint)
632
+ searchHints: buildSearchHints(ann.fingerprint),
633
+ lifecycle: ann.lifecycle.map((ev) => {
634
+ const out = {
635
+ type: ev.type,
636
+ actor: ev.actor,
637
+ timestamp: ev.timestamp
638
+ };
639
+ if (ev.reason !== void 0) out.reason = ev.reason;
640
+ return out;
641
+ })
615
642
  }));
616
643
  return {
617
644
  version: 1,
@@ -621,6 +648,67 @@ function generateAgentExport(annotations, viewportBucket) {
621
648
  };
622
649
  }
623
650
 
651
+ // src/core/lifecycle.ts
652
+ var InvalidTransitionError = class extends Error {
653
+ constructor(from, action) {
654
+ super(`Cannot ${action} from status "${from}"`);
655
+ this.name = "InvalidTransitionError";
656
+ }
657
+ };
658
+ var ACTION_TO_EVENT = {
659
+ acknowledge: "acknowledged",
660
+ claimFix: "fix_claimed",
661
+ verify: "verified",
662
+ reject: "rejected",
663
+ dismiss: "dismissed",
664
+ reopen: "reopened"
665
+ };
666
+ var DEFAULT_ACTOR_BY_EVENT = {
667
+ created: "designer",
668
+ acknowledged: "developer",
669
+ fix_claimed: "agent",
670
+ verified: "developer",
671
+ rejected: "developer",
672
+ dismissed: "developer",
673
+ reopened: "developer",
674
+ migrated: null
675
+ };
676
+ function createEvent(type, opts = {}) {
677
+ var _a3, _b;
678
+ const event = {
679
+ type,
680
+ actor: (_a3 = opts.actor) != null ? _a3 : DEFAULT_ACTOR_BY_EVENT[type],
681
+ timestamp: (_b = opts.timestamp) != null ? _b : Date.now()
682
+ };
683
+ if (opts.actorName !== void 0) event.actorName = opts.actorName;
684
+ if (opts.reason !== void 0) event.reason = opts.reason;
685
+ return event;
686
+ }
687
+ function nextStatus(from, action) {
688
+ switch (action) {
689
+ case "acknowledge":
690
+ return from === "pending" ? "in_progress" : null;
691
+ case "claimFix":
692
+ return from === "pending" || from === "in_progress" ? "fixed_unverified" : null;
693
+ case "verify":
694
+ return from === "fixed_unverified" || from === "in_progress" ? "verified" : null;
695
+ case "reject":
696
+ return from === "fixed_unverified" ? "pending" : null;
697
+ case "dismiss":
698
+ return from === "pending" || from === "in_progress" || from === "fixed_unverified" ? "dismissed" : null;
699
+ case "reopen":
700
+ return from === "dismissed" || from === "verified" ? "pending" : null;
701
+ }
702
+ }
703
+ function transition(annotation, action, opts = {}) {
704
+ const next = nextStatus(annotation.status, action);
705
+ if (next === null) {
706
+ throw new InvalidTransitionError(annotation.status, action);
707
+ }
708
+ const event = createEvent(ACTION_TO_EVENT[action], opts);
709
+ return { status: next, event };
710
+ }
711
+
624
712
  // src/ui/styles.ts
625
713
  var STYLES_ID = "data-remarq-styles";
626
714
  var CSS = `
@@ -635,6 +723,11 @@ var CSS = `
635
723
  --remarq-resolved: #22c55e;
636
724
  --remarq-overlay: rgba(59, 130, 246, 0.15);
637
725
  --remarq-shadow: 0 4px 12px rgba(0,0,0,0.15);
726
+ --remarq-status-pending: #f97316;
727
+ --remarq-status-in-progress: #eab308;
728
+ --remarq-status-fixed-unverified: #3b82f6;
729
+ --remarq-status-verified: #22c55e;
730
+ --remarq-status-dismissed: #6b7280;
638
731
  }
639
732
 
640
733
  [data-remarq-theme="dark"] {
@@ -648,6 +741,11 @@ var CSS = `
648
741
  --remarq-resolved: #4ade80;
649
742
  --remarq-overlay: rgba(96, 165, 250, 0.15);
650
743
  --remarq-shadow: 0 4px 12px rgba(0,0,0,0.4);
744
+ --remarq-status-pending: #fb923c;
745
+ --remarq-status-in-progress: #facc15;
746
+ --remarq-status-fixed-unverified: #60a5fa;
747
+ --remarq-status-verified: #4ade80;
748
+ --remarq-status-dismissed: #9ca3af;
651
749
  }
652
750
 
653
751
  .remarq-toolbar {
@@ -691,19 +789,29 @@ var CSS = `
691
789
 
692
790
  .remarq-badge {
693
791
  position: absolute;
694
- top: -4px;
695
- right: -4px;
696
- min-width: 16px;
697
- height: 16px;
698
- padding: 0 4px;
699
- border-radius: 8px;
792
+ top: -6px;
793
+ right: -6px;
794
+ width: 18px;
795
+ height: 18px;
796
+ padding: 0;
797
+ border-radius: 50%;
700
798
  background: var(--remarq-pending);
701
799
  color: #ffffff;
702
800
  font-size: 10px;
703
801
  font-weight: 600;
802
+ line-height: 1;
704
803
  display: flex;
705
804
  align-items: center;
706
805
  justify-content: center;
806
+ box-sizing: border-box;
807
+ }
808
+
809
+ .remarq-toolbar-badge--verification {
810
+ top: auto;
811
+ bottom: -6px;
812
+ right: -6px;
813
+ background: var(--remarq-status-fixed-unverified);
814
+ cursor: pointer;
707
815
  }
708
816
 
709
817
  .remarq-overlay {
@@ -753,8 +861,11 @@ var CSS = `
753
861
  }
754
862
 
755
863
  .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; }
864
+ .remarq-marker--pending { background: var(--remarq-status-pending); }
865
+ .remarq-marker--in-progress { background: var(--remarq-status-in-progress); }
866
+ .remarq-marker--fixed-unverified { background: var(--remarq-status-fixed-unverified); }
867
+ .remarq-marker--verified { background: var(--remarq-status-verified); opacity: 0.7; }
868
+ .remarq-marker--dismissed { background: var(--remarq-status-dismissed); opacity: 0.5; }
758
869
 
759
870
  .remarq-popup {
760
871
  position: absolute;
@@ -799,14 +910,30 @@ var CSS = `
799
910
 
800
911
  .remarq-popup-actions {
801
912
  display: flex;
802
- justify-content: flex-end;
803
- gap: 8px;
913
+ flex-direction: column;
914
+ gap: 6px;
804
915
  padding: 8px 12px;
805
916
  border-top: 1px solid var(--remarq-border);
806
917
  }
807
918
 
919
+ .remarq-popup-actions-row {
920
+ display: flex;
921
+ flex-wrap: wrap;
922
+ gap: 6px;
923
+ }
924
+
925
+ .remarq-popup-actions-row--transitions {
926
+ justify-content: flex-start;
927
+ }
928
+
929
+ .remarq-popup-actions-row--utility {
930
+ justify-content: flex-end;
931
+ padding-top: 6px;
932
+ border-top: 1px dashed var(--remarq-border);
933
+ }
934
+
808
935
  .remarq-popup-actions button {
809
- padding: 4px 12px;
936
+ padding: 5px 12px;
810
937
  border: 1px solid var(--remarq-border);
811
938
  border-radius: 4px;
812
939
  background: var(--remarq-bg);
@@ -821,6 +948,19 @@ var CSS = `
821
948
  color: #ffffff;
822
949
  }
823
950
 
951
+ .remarq-popup-utility-btn {
952
+ font-size: 11px !important;
953
+ padding: 3px 10px !important;
954
+ color: var(--remarq-text-secondary) !important;
955
+ background: transparent !important;
956
+ border-color: transparent !important;
957
+ }
958
+
959
+ .remarq-popup-utility-btn:hover {
960
+ background: var(--remarq-bg-secondary) !important;
961
+ color: var(--remarq-text) !important;
962
+ }
963
+
824
964
  .remarq-detached-panel {
825
965
  position: fixed;
826
966
  z-index: 2147483646;
@@ -1079,6 +1219,51 @@ var CSS = `
1079
1219
  color: var(--remarq-text-secondary);
1080
1220
  margin-top: 4px;
1081
1221
  }
1222
+
1223
+ .remarq-popup-history {
1224
+ margin-top: 8px;
1225
+ font-size: 12px;
1226
+ color: var(--remarq-text-secondary);
1227
+ }
1228
+
1229
+ .remarq-popup-history summary {
1230
+ cursor: pointer;
1231
+ padding: 4px 0;
1232
+ user-select: none;
1233
+ }
1234
+
1235
+ .remarq-popup-history-list {
1236
+ list-style: none;
1237
+ margin: 4px 0 0 0;
1238
+ padding: 0;
1239
+ }
1240
+
1241
+ .remarq-popup-history-list li {
1242
+ padding: 2px 0;
1243
+ font-size: 11px;
1244
+ line-height: 1.4;
1245
+ }
1246
+
1247
+ .remarq-popup-reason {
1248
+ width: 100%;
1249
+ min-height: 50px;
1250
+ margin-bottom: 8px;
1251
+ padding: 6px;
1252
+ border: 1px solid var(--remarq-border);
1253
+ border-radius: 4px;
1254
+ background: var(--remarq-bg-secondary);
1255
+ color: var(--remarq-text);
1256
+ font-family: inherit;
1257
+ font-size: 12px;
1258
+ resize: vertical;
1259
+ box-sizing: border-box;
1260
+ }
1261
+
1262
+ .remarq-popup-reason-row {
1263
+ display: flex;
1264
+ justify-content: flex-end;
1265
+ gap: 8px;
1266
+ }
1082
1267
  `;
1083
1268
  function injectStyles() {
1084
1269
  if (document.querySelector(`style[${STYLES_ID}]`)) return;
@@ -1186,8 +1371,19 @@ var Toolbar = class {
1186
1371
  this.inspectBtn = this.createButton("inspect", ICONS.inspect, () => callbacks.onInspect());
1187
1372
  this.badgeEl = document.createElement("span");
1188
1373
  this.badgeEl.className = "remarq-badge";
1374
+ this.badgeEl.title = "Needs attention";
1189
1375
  this.badgeEl.style.display = "none";
1190
1376
  this.inspectBtn.appendChild(this.badgeEl);
1377
+ this.verificationBadgeEl = document.createElement("span");
1378
+ this.verificationBadgeEl.className = "remarq-badge remarq-toolbar-badge--verification";
1379
+ this.verificationBadgeEl.title = "Pending your verification";
1380
+ this.verificationBadgeEl.style.display = "none";
1381
+ this.verificationBadgeEl.addEventListener("click", (e) => {
1382
+ var _a3;
1383
+ e.stopPropagation();
1384
+ (_a3 = callbacks.onVerificationBadgeClick) == null ? void 0 : _a3.call(callbacks);
1385
+ });
1386
+ this.inspectBtn.appendChild(this.verificationBadgeEl);
1191
1387
  this.spacingBtn = this.createButton("spacing", ICONS.spacing, () => callbacks.onSpacingToggle());
1192
1388
  this.spacingBtn.disabled = true;
1193
1389
  const copyBtn = this.createButton("copy", ICONS.copy, () => callbacks.onCopy());
@@ -1234,6 +1430,10 @@ var Toolbar = class {
1234
1430
  this.badgeEl.textContent = String(count);
1235
1431
  this.badgeEl.style.display = count > 0 ? "flex" : "none";
1236
1432
  }
1433
+ setVerificationBadgeCount(count) {
1434
+ this.verificationBadgeEl.textContent = String(count);
1435
+ this.verificationBadgeEl.style.display = count > 0 ? "flex" : "none";
1436
+ }
1237
1437
  getFileInput() {
1238
1438
  return this.fileInput;
1239
1439
  }
@@ -1717,6 +1917,47 @@ var SpacingOverlay = class {
1717
1917
  };
1718
1918
 
1719
1919
  // src/ui/popup.ts
1920
+ var STATUS_LABEL = {
1921
+ pending: "Pending",
1922
+ in_progress: "In progress",
1923
+ fixed_unverified: "Fix claimed",
1924
+ verified: "Verified",
1925
+ dismissed: "Dismissed"
1926
+ };
1927
+ var EVENT_LABEL = {
1928
+ created: "Created",
1929
+ acknowledged: "In progress",
1930
+ fix_claimed: "Fix claimed",
1931
+ verified: "Verified",
1932
+ rejected: "Rejected",
1933
+ dismissed: "Dismissed",
1934
+ reopened: "Reopened",
1935
+ migrated: "Migrated"
1936
+ };
1937
+ function actionsForStatus(status) {
1938
+ switch (status) {
1939
+ case "pending":
1940
+ return [
1941
+ { label: "Acknowledge", action: "acknowledge", primary: true },
1942
+ { label: "Dismiss", action: "dismiss", needsReason: true }
1943
+ ];
1944
+ case "in_progress":
1945
+ return [
1946
+ { label: "Mark verified", action: "verify", primary: true },
1947
+ { label: "Dismiss", action: "dismiss", needsReason: true }
1948
+ ];
1949
+ case "fixed_unverified":
1950
+ return [
1951
+ { label: "Verify", action: "verify", primary: true },
1952
+ { label: "Reject", action: "reject", needsReason: true },
1953
+ { label: "Dismiss", action: "dismiss", needsReason: true }
1954
+ ];
1955
+ case "verified":
1956
+ return [{ label: "Reopen", action: "reopen" }];
1957
+ case "dismissed":
1958
+ return [{ label: "Reopen", action: "reopen" }];
1959
+ }
1960
+ }
1720
1961
  var POPUP_WIDTH = 300;
1721
1962
  var POPUP_MARGIN = 8;
1722
1963
  var Popup = class {
@@ -1725,6 +1966,7 @@ var Popup = class {
1725
1966
  this.popupEl = null;
1726
1967
  this.keyHandler = null;
1727
1968
  this.outsideClickHandler = null;
1969
+ this.pendingEditFlush = null;
1728
1970
  }
1729
1971
  show(info, position, onSubmit, onCancel) {
1730
1972
  this.hide();
@@ -1809,7 +2051,7 @@ var Popup = class {
1809
2051
  popup2.className = "remarq-popup";
1810
2052
  const header = document.createElement("div");
1811
2053
  header.className = "remarq-popup-header";
1812
- header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${info.status}]`;
2054
+ header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${STATUS_LABEL[info.status]}]`;
1813
2055
  const body = document.createElement("div");
1814
2056
  body.className = "remarq-popup-body";
1815
2057
  const makeCommentEl = () => {
@@ -1821,38 +2063,10 @@ var Popup = class {
1821
2063
  return el;
1822
2064
  };
1823
2065
  body.appendChild(makeCommentEl());
2066
+ body.appendChild(this.buildLifecycleViewer(info.lifecycle));
1824
2067
  const actions = document.createElement("div");
1825
2068
  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);
2069
+ this.renderActionButtons(actions, info, callbacks);
1856
2070
  popup2.appendChild(header);
1857
2071
  popup2.appendChild(body);
1858
2072
  popup2.appendChild(actions);
@@ -1879,7 +2093,103 @@ var Popup = class {
1879
2093
  document.addEventListener("mousedown", this.outsideClickHandler);
1880
2094
  }, 0);
1881
2095
  }
2096
+ buildLifecycleViewer(lifecycle) {
2097
+ var _a3, _b;
2098
+ const details = document.createElement("details");
2099
+ details.className = "remarq-popup-history";
2100
+ const summary = document.createElement("summary");
2101
+ summary.textContent = `History (${lifecycle.length})`;
2102
+ details.appendChild(summary);
2103
+ const list = document.createElement("ul");
2104
+ list.className = "remarq-popup-history-list";
2105
+ for (const ev of lifecycle) {
2106
+ const li = document.createElement("li");
2107
+ const when = new Date(ev.timestamp).toLocaleString();
2108
+ const who = (_a3 = ev.actor) != null ? _a3 : "system";
2109
+ const what = (_b = EVENT_LABEL[ev.type]) != null ? _b : ev.type;
2110
+ let text = `${when} \xB7 ${who} \xB7 ${what}`;
2111
+ if (ev.reason) text += ` \u2014 ${ev.reason}`;
2112
+ li.textContent = text;
2113
+ list.appendChild(li);
2114
+ }
2115
+ details.appendChild(list);
2116
+ return details;
2117
+ }
2118
+ renderActionButtons(container, info, callbacks) {
2119
+ container.replaceChildren();
2120
+ const transitions = document.createElement("div");
2121
+ transitions.className = "remarq-popup-actions-row remarq-popup-actions-row--transitions";
2122
+ for (const def of actionsForStatus(info.status)) {
2123
+ const btn = document.createElement("button");
2124
+ btn.textContent = def.label;
2125
+ if (def.primary) btn.className = "remarq-primary";
2126
+ btn.addEventListener("click", () => {
2127
+ if (def.needsReason) {
2128
+ this.showReasonInput(container, info, callbacks, def);
2129
+ } else {
2130
+ this.hide();
2131
+ callbacks.onTransition(def.action);
2132
+ }
2133
+ });
2134
+ transitions.appendChild(btn);
2135
+ }
2136
+ container.appendChild(transitions);
2137
+ const utility = document.createElement("div");
2138
+ utility.className = "remarq-popup-actions-row remarq-popup-actions-row--utility";
2139
+ const copyBtn = document.createElement("button");
2140
+ copyBtn.className = "remarq-popup-utility-btn";
2141
+ copyBtn.textContent = "Copy";
2142
+ copyBtn.addEventListener("click", () => callbacks.onCopy());
2143
+ utility.appendChild(copyBtn);
2144
+ const deleteBtn = document.createElement("button");
2145
+ deleteBtn.className = "remarq-popup-utility-btn";
2146
+ deleteBtn.textContent = "Delete";
2147
+ deleteBtn.addEventListener("click", () => {
2148
+ this.hide();
2149
+ callbacks.onDelete();
2150
+ });
2151
+ utility.appendChild(deleteBtn);
2152
+ const closeBtn = document.createElement("button");
2153
+ closeBtn.className = "remarq-popup-utility-btn";
2154
+ closeBtn.textContent = "Close";
2155
+ closeBtn.addEventListener("click", () => {
2156
+ this.hide();
2157
+ callbacks.onClose();
2158
+ });
2159
+ utility.appendChild(closeBtn);
2160
+ container.appendChild(utility);
2161
+ }
2162
+ showReasonInput(container, info, callbacks, def) {
2163
+ container.replaceChildren();
2164
+ const textarea = document.createElement("textarea");
2165
+ textarea.placeholder = `Reason for ${def.label.toLowerCase()} (optional)\u2026`;
2166
+ textarea.className = "remarq-popup-reason";
2167
+ container.appendChild(textarea);
2168
+ const row = document.createElement("div");
2169
+ row.className = "remarq-popup-reason-row";
2170
+ const cancel = document.createElement("button");
2171
+ cancel.textContent = "Cancel";
2172
+ cancel.addEventListener("click", () => {
2173
+ this.renderActionButtons(container, info, callbacks);
2174
+ });
2175
+ const submit = document.createElement("button");
2176
+ submit.className = "remarq-primary";
2177
+ submit.textContent = "Submit";
2178
+ submit.addEventListener("click", () => {
2179
+ const reason = textarea.value.trim() || void 0;
2180
+ this.hide();
2181
+ callbacks.onTransition(def.action, reason);
2182
+ });
2183
+ row.appendChild(cancel);
2184
+ row.appendChild(submit);
2185
+ container.appendChild(row);
2186
+ textarea.focus();
2187
+ }
1882
2188
  hide() {
2189
+ if (this.pendingEditFlush) {
2190
+ this.pendingEditFlush();
2191
+ this.pendingEditFlush = null;
2192
+ }
1883
2193
  if (this.popupEl) {
1884
2194
  this.popupEl.remove();
1885
2195
  this.popupEl = null;
@@ -1914,12 +2224,8 @@ var Popup = class {
1914
2224
  commentEl.replaceWith(textarea);
1915
2225
  textarea.focus();
1916
2226
  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
- }
2227
+ const restoreView = () => {
2228
+ if (!textarea.isConnected) return;
1923
2229
  const restored = document.createElement("div");
1924
2230
  restored.textContent = info.comment;
1925
2231
  restored.style.cursor = "pointer";
@@ -1927,26 +2233,33 @@ var Popup = class {
1927
2233
  restored.addEventListener("click", () => this.enterEditMode(restored, info, callbacks));
1928
2234
  textarea.replaceWith(restored);
1929
2235
  };
2236
+ const commitEdit = () => {
2237
+ this.pendingEditFlush = null;
2238
+ const newComment = textarea.value.trim();
2239
+ if (newComment && newComment !== info.comment) {
2240
+ info.comment = newComment;
2241
+ callbacks.onEdit(newComment);
2242
+ }
2243
+ restoreView();
2244
+ };
2245
+ const cancelEdit = () => {
2246
+ this.pendingEditFlush = null;
2247
+ restoreView();
2248
+ };
2249
+ this.pendingEditFlush = commitEdit;
1930
2250
  textarea.addEventListener("keydown", (e) => {
1931
2251
  if (e.key === "Enter" && !e.shiftKey) {
1932
2252
  e.preventDefault();
1933
- saveEdit();
2253
+ commitEdit();
1934
2254
  }
1935
2255
  if (e.key === "Escape") {
1936
2256
  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);
2257
+ cancelEdit();
1943
2258
  }
1944
2259
  });
1945
2260
  textarea.addEventListener("blur", () => {
1946
2261
  setTimeout(() => {
1947
- if (textarea.isConnected) {
1948
- saveEdit();
1949
- }
2262
+ if (textarea.isConnected) commitEdit();
1950
2263
  }, 50);
1951
2264
  });
1952
2265
  }
@@ -1974,6 +2287,16 @@ var Popup = class {
1974
2287
  };
1975
2288
 
1976
2289
  // src/ui/markers.ts
2290
+ var STATUS_COLOR = {
2291
+ pending: "var(--remarq-status-pending)",
2292
+ in_progress: "var(--remarq-status-in-progress)",
2293
+ fixed_unverified: "var(--remarq-status-fixed-unverified)",
2294
+ verified: "var(--remarq-status-verified)",
2295
+ dismissed: "var(--remarq-status-dismissed)"
2296
+ };
2297
+ function statusClass(status) {
2298
+ return `remarq-marker--${status.replace("_", "-")}`;
2299
+ }
1977
2300
  var MarkerManager = class {
1978
2301
  constructor(container, onClick) {
1979
2302
  this.container = container;
@@ -1986,7 +2309,7 @@ var MarkerManager = class {
1986
2309
  addMarker(annotation, target) {
1987
2310
  this.counter++;
1988
2311
  const markerEl = document.createElement("div");
1989
- markerEl.className = "remarq-marker";
2312
+ markerEl.className = `remarq-marker ${statusClass(annotation.status)}`;
1990
2313
  markerEl.setAttribute("data-status", annotation.status);
1991
2314
  markerEl.setAttribute("data-annotation-id", annotation.id);
1992
2315
  markerEl.textContent = String(this.counter);
@@ -2012,7 +2335,17 @@ var MarkerManager = class {
2012
2335
  const entry = this.markers.get(id);
2013
2336
  if (entry) {
2014
2337
  entry.annotation.status = status;
2338
+ entry.markerEl.className = `remarq-marker ${statusClass(status)}`;
2015
2339
  entry.markerEl.setAttribute("data-status", status);
2340
+ this.applyOutline(entry.target, status);
2341
+ }
2342
+ }
2343
+ scrollToMarker(id) {
2344
+ const entry = this.markers.get(id);
2345
+ if (!entry) return;
2346
+ try {
2347
+ entry.target.scrollIntoView({ behavior: "smooth", block: "center" });
2348
+ } catch (e) {
2016
2349
  }
2017
2350
  }
2018
2351
  clear() {
@@ -2031,7 +2364,7 @@ var MarkerManager = class {
2031
2364
  this.clear();
2032
2365
  }
2033
2366
  applyOutline(target, status) {
2034
- const color = status === "pending" ? "#f97316" : "rgba(34, 197, 94, 0.5)";
2367
+ const color = STATUS_COLOR[status];
2035
2368
  target.style.outline = `2px solid ${color}`;
2036
2369
  target.style.outlineOffset = "2px";
2037
2370
  }
@@ -2378,8 +2711,18 @@ function refreshMarkers() {
2378
2711
  markers.addMarker(ann, el);
2379
2712
  }
2380
2713
  detachedPanel.update(otherBreakpoint, detached);
2381
- const pendingCount = anns.filter((a) => a.status === "pending").length;
2382
- toolbar.setBadgeCount(pendingCount);
2714
+ const needsAttention = anns.filter(
2715
+ (a) => a.status === "pending" || a.status === "in_progress"
2716
+ ).length;
2717
+ const needsVerification = anns.filter((a) => a.status === "fixed_unverified").length;
2718
+ toolbar.setBadgeCount(needsAttention);
2719
+ toolbar.setVerificationBadgeCount(needsVerification);
2720
+ }
2721
+ function jumpToFirstUnverified() {
2722
+ if (!storage || !markers) return;
2723
+ const ann = storage.getByRoute(currentRoute()).find((a) => a.status === "fixed_unverified");
2724
+ if (!ann) return;
2725
+ markers.scrollToMarker(ann.id);
2383
2726
  }
2384
2727
  function scheduleRefresh() {
2385
2728
  if (refreshScheduled) return;
@@ -2412,6 +2755,7 @@ function handleInspectClick(e) {
2412
2755
  classFilter: options.classFilter,
2413
2756
  dataAttribute: options.dataAttribute
2414
2757
  });
2758
+ const now = Date.now();
2415
2759
  const ann = {
2416
2760
  id: generateId(),
2417
2761
  comment,
@@ -2419,8 +2763,9 @@ function handleInspectClick(e) {
2419
2763
  route: currentRoute(),
2420
2764
  viewport: `${window.innerWidth}x${window.innerHeight}`,
2421
2765
  viewportBucket: toBucket(window.innerWidth),
2422
- timestamp: Date.now(),
2423
- status: "pending"
2766
+ timestamp: now,
2767
+ status: "pending",
2768
+ lifecycle: [{ type: "created", actor: "designer", timestamp: now }]
2424
2769
  };
2425
2770
  cacheElement(ann.id, target);
2426
2771
  storage.add(ann);
@@ -2512,7 +2857,8 @@ function handleMarkerClick(annotationId) {
2512
2857
  tag: ann.fingerprint.tagName,
2513
2858
  text: (_a3 = ann.fingerprint.textContent) != null ? _a3 : "",
2514
2859
  comment: ann.comment,
2515
- status: ann.status
2860
+ status: ann.status,
2861
+ lifecycle: ann.lifecycle
2516
2862
  },
2517
2863
  {
2518
2864
  top: window.scrollY + rect.bottom + 8,
@@ -2520,9 +2866,8 @@ function handleMarkerClick(annotationId) {
2520
2866
  anchorBottom: window.scrollY + rect.top - 8
2521
2867
  },
2522
2868
  {
2523
- onResolve: () => {
2524
- storage.update(ann.id, { status: "resolved" });
2525
- refreshMarkers();
2869
+ onTransition: (action, reason) => {
2870
+ applyTransition(ann.id, action, reason ? { reason } : void 0);
2526
2871
  },
2527
2872
  onDelete: () => {
2528
2873
  elementCache.delete(ann.id);
@@ -2536,12 +2881,14 @@ function handleMarkerClick(annotationId) {
2536
2881
  refreshMarkers();
2537
2882
  },
2538
2883
  onCopy: () => {
2539
- const fp = ann.fingerprint;
2884
+ var _a4;
2885
+ const fresh = (_a4 = storage.getById(ann.id)) != null ? _a4 : ann;
2886
+ const fp = fresh.fingerprint;
2540
2887
  const lines = [
2541
- `[${ann.status}] "${ann.comment}"`,
2888
+ `[${fresh.status}] "${fresh.comment}"`,
2542
2889
  `Element: <${fp.tagName}>${fp.textContent ? ` "${fp.textContent}"` : ""}`,
2543
- `Route: ${ann.route}`,
2544
- `Viewport: ${ann.viewportBucket}px`
2890
+ `Route: ${fresh.route}`,
2891
+ `Viewport: ${fresh.viewportBucket}px`
2545
2892
  ];
2546
2893
  if (fp.sourceLocation) lines.push(`Source: ${fp.sourceLocation}`);
2547
2894
  navigator.clipboard.writeText(lines.join("\n")).then(() => {
@@ -2676,6 +3023,16 @@ function copyAgentToClipboard() {
2676
3023
  console.warn("[web-remarq] Clipboard write failed");
2677
3024
  });
2678
3025
  }
3026
+ function applyTransition(id, action, opts = {}) {
3027
+ if (!storage) return;
3028
+ const ann = storage.getById(id);
3029
+ if (!ann) return;
3030
+ const { status, event } = transition(ann, action, opts);
3031
+ const lifecycle = [...ann.lifecycle, event];
3032
+ storage.update(id, { status, lifecycle });
3033
+ markers == null ? void 0 : markers.updateStatus(id, status);
3034
+ refreshMarkers();
3035
+ }
2679
3036
  function setupMutationObserver() {
2680
3037
  mutationObserver = new MutationObserver((mutations) => {
2681
3038
  let hasExternalMutation = false;
@@ -2737,7 +3094,8 @@ var WebRemarq = {
2737
3094
  showToast(themeManager.container, "All annotations cleared");
2738
3095
  },
2739
3096
  onThemeToggle: () => themeManager.toggle(),
2740
- onHelp: () => showShortcutsModal(themeManager.container)
3097
+ onHelp: () => showShortcutsModal(themeManager.container),
3098
+ onVerificationBadgeClick: jumpToFirstUnverified
2741
3099
  }, position);
2742
3100
  routeObserver = new RouteObserver();
2743
3101
  unsubRoute = routeObserver.onChange(() => refreshMarkers());
@@ -2831,6 +3189,28 @@ var WebRemarq = {
2831
3189
  elementCache.clear();
2832
3190
  storage == null ? void 0 : storage.clearAll();
2833
3191
  if (initialized) refreshMarkers();
3192
+ },
3193
+ acknowledge(id, opts) {
3194
+ applyTransition(id, "acknowledge", opts);
3195
+ },
3196
+ claimFix(id, opts) {
3197
+ applyTransition(id, "claimFix", opts);
3198
+ },
3199
+ verify(id, opts) {
3200
+ applyTransition(id, "verify", opts);
3201
+ },
3202
+ reject(id, opts) {
3203
+ applyTransition(id, "reject", opts);
3204
+ },
3205
+ dismiss(id, opts) {
3206
+ applyTransition(id, "dismiss", opts);
3207
+ },
3208
+ reopen(id, opts) {
3209
+ applyTransition(id, "reopen", opts);
3210
+ },
3211
+ /** @deprecated Use verify() instead. */
3212
+ markResolved(id) {
3213
+ applyTransition(id, "verify");
2834
3214
  }
2835
3215
  };
2836
3216
  // Annotate the CommonJS export names for ESM import in node: