web-remarq 0.5.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
@@ -49,6 +49,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
49
49
  // src/index.ts
50
50
  var src_exports = {};
51
51
  __export(src_exports, {
52
+ LocalStorageAdapter: () => LocalStorageAdapter,
52
53
  WebRemarq: () => WebRemarq
53
54
  });
54
55
  module.exports = __toCommonJS(src_exports);
@@ -88,79 +89,151 @@ function destroyViewportListener() {
88
89
  }
89
90
 
90
91
  // src/core/storage.ts
91
- var STORAGE_KEY = "remarq:annotations";
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
- constructor() {
94
- this.annotations = [];
95
- this.extraFields = {};
96
- this.isMemoryOnly = false;
97
- this.load();
108
+ constructor(adapter) {
109
+ this.adapter = adapter;
110
+ this.cache = [];
111
+ this.ready = this.init();
112
+ }
113
+ get isMemoryOnly() {
114
+ var _a3;
115
+ return (_a3 = this.adapter.isMemoryOnly) != null ? _a3 : false;
98
116
  }
99
117
  getAll() {
100
- return [...this.annotations];
118
+ return [...this.cache];
101
119
  }
102
120
  getByRoute(route) {
103
- return this.annotations.filter((a) => a.route === route);
121
+ return this.cache.filter((a) => a.route === route);
104
122
  }
105
- add(annotation) {
106
- this.annotations.push(annotation);
107
- this.save();
123
+ getById(id) {
124
+ return this.cache.find((a) => a.id === id);
108
125
  }
109
- remove(id) {
110
- this.annotations = this.annotations.filter((a) => a.id !== id);
111
- this.save();
126
+ async add(annotation) {
127
+ this.cache.push(annotation);
128
+ await this.adapter.save(annotation);
112
129
  }
113
- update(id, changes) {
114
- const idx = this.annotations.findIndex((a) => a.id === id);
115
- if (idx !== -1) {
116
- this.annotations[idx] = __spreadValues(__spreadValues({}, this.annotations[idx]), changes);
117
- this.save();
118
- }
130
+ async remove(id) {
131
+ this.cache = this.cache.filter((a) => a.id !== id);
132
+ await this.adapter.remove(id);
119
133
  }
120
- clearAll() {
121
- this.annotations = [];
122
- this.save();
134
+ async update(id, changes) {
135
+ const idx = this.cache.findIndex((a) => a.id === id);
136
+ if (idx === -1) return;
137
+ const updated = __spreadValues(__spreadValues({}, this.cache[idx]), changes);
138
+ this.cache[idx] = updated;
139
+ await this.adapter.save(updated);
140
+ }
141
+ async clearAll() {
142
+ this.cache = [];
143
+ await this.adapter.clear();
123
144
  }
124
145
  exportJSON() {
125
146
  return {
126
147
  version: 1,
127
- annotations: [...this.annotations]
148
+ annotations: [...this.cache]
128
149
  };
129
150
  }
130
- importJSON(data) {
131
- this.annotations = [...data.annotations];
151
+ async importJSON(data) {
152
+ this.cache = data.annotations.map(migrateAnnotation);
132
153
  this.migrateViewportBuckets();
133
- this.save();
154
+ await this.adapter.clear();
155
+ for (const ann of this.cache) {
156
+ await this.adapter.save(ann);
157
+ }
158
+ }
159
+ async init() {
160
+ const data = await this.adapter.load();
161
+ if (data) {
162
+ this.cache = data.annotations.map(migrateAnnotation);
163
+ this.migrateViewportBuckets();
164
+ }
134
165
  }
135
166
  migrateViewportBuckets() {
136
- for (const ann of this.annotations) {
167
+ for (const ann of this.cache) {
137
168
  if (ann.viewportBucket == null && ann.viewport) {
138
169
  const width = parseInt(ann.viewport.split("x")[0], 10);
139
170
  ann.viewportBucket = toBucket(width);
140
171
  }
141
172
  }
142
173
  }
143
- load() {
174
+ };
175
+
176
+ // src/core/local-storage-adapter.ts
177
+ var STORAGE_KEY = "remarq:annotations";
178
+ var LocalStorageAdapter = class {
179
+ constructor() {
180
+ this.isMemoryOnly = false;
181
+ this.extraFields = {};
182
+ this.memoryStore = null;
183
+ }
184
+ async load() {
185
+ if (this.isMemoryOnly) return this.memoryStore;
144
186
  try {
145
187
  const raw = localStorage.getItem(STORAGE_KEY);
146
- if (raw) {
147
- const parsed = JSON.parse(raw);
148
- const _a3 = parsed, { version, annotations } = _a3, rest = __objRest(_a3, ["version", "annotations"]);
149
- this.annotations = annotations != null ? annotations : [];
150
- this.extraFields = rest;
151
- this.migrateViewportBuckets();
152
- }
188
+ if (!raw) return null;
189
+ const parsed = JSON.parse(raw);
190
+ const _a3 = parsed, { version, annotations } = _a3, rest = __objRest(_a3, ["version", "annotations"]);
191
+ this.extraFields = rest;
192
+ const store = {
193
+ version: 1,
194
+ annotations: Array.isArray(annotations) ? annotations : []
195
+ };
196
+ this.memoryStore = store;
197
+ return store;
153
198
  } catch (e) {
154
199
  this.isMemoryOnly = true;
200
+ return this.memoryStore;
201
+ }
202
+ }
203
+ async save(annotation) {
204
+ const store = await this.ensureStore();
205
+ const idx = store.annotations.findIndex((a) => a.id === annotation.id);
206
+ if (idx === -1) {
207
+ store.annotations.push(annotation);
208
+ } else {
209
+ store.annotations[idx] = annotation;
155
210
  }
211
+ this.persist(store);
212
+ }
213
+ async remove(id) {
214
+ const store = await this.ensureStore();
215
+ store.annotations = store.annotations.filter((a) => a.id !== id);
216
+ this.persist(store);
217
+ }
218
+ async clear() {
219
+ const store = await this.ensureStore();
220
+ store.annotations = [];
221
+ this.persist(store);
156
222
  }
157
- save() {
223
+ async ensureStore() {
224
+ if (this.memoryStore) return this.memoryStore;
225
+ const loaded = await this.load();
226
+ if (loaded) return loaded;
227
+ this.memoryStore = { version: 1, annotations: [] };
228
+ return this.memoryStore;
229
+ }
230
+ persist(store) {
158
231
  if (this.isMemoryOnly) return;
159
232
  try {
160
233
  const data = __spreadProps(__spreadValues({
161
234
  version: 1
162
235
  }, this.extraFields), {
163
- annotations: this.annotations
236
+ annotations: store.annotations
164
237
  });
165
238
  localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
166
239
  } catch (e) {
@@ -556,7 +629,16 @@ function generateAgentExport(annotations, viewportBucket) {
556
629
  status: ann.status,
557
630
  timestamp: ann.timestamp,
558
631
  source: resolveSource(ann.fingerprint),
559
- 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
+ })
560
642
  }));
561
643
  return {
562
644
  version: 1,
@@ -566,6 +648,67 @@ function generateAgentExport(annotations, viewportBucket) {
566
648
  };
567
649
  }
568
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
+
569
712
  // src/ui/styles.ts
570
713
  var STYLES_ID = "data-remarq-styles";
571
714
  var CSS = `
@@ -580,6 +723,11 @@ var CSS = `
580
723
  --remarq-resolved: #22c55e;
581
724
  --remarq-overlay: rgba(59, 130, 246, 0.15);
582
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;
583
731
  }
584
732
 
585
733
  [data-remarq-theme="dark"] {
@@ -593,6 +741,11 @@ var CSS = `
593
741
  --remarq-resolved: #4ade80;
594
742
  --remarq-overlay: rgba(96, 165, 250, 0.15);
595
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;
596
749
  }
597
750
 
598
751
  .remarq-toolbar {
@@ -636,19 +789,29 @@ var CSS = `
636
789
 
637
790
  .remarq-badge {
638
791
  position: absolute;
639
- top: -4px;
640
- right: -4px;
641
- min-width: 16px;
642
- height: 16px;
643
- padding: 0 4px;
644
- border-radius: 8px;
792
+ top: -6px;
793
+ right: -6px;
794
+ width: 18px;
795
+ height: 18px;
796
+ padding: 0;
797
+ border-radius: 50%;
645
798
  background: var(--remarq-pending);
646
799
  color: #ffffff;
647
800
  font-size: 10px;
648
801
  font-weight: 600;
802
+ line-height: 1;
649
803
  display: flex;
650
804
  align-items: center;
651
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;
652
815
  }
653
816
 
654
817
  .remarq-overlay {
@@ -698,8 +861,11 @@ var CSS = `
698
861
  }
699
862
 
700
863
  .remarq-marker:hover { transform: scale(1.2); }
701
- .remarq-marker[data-status="pending"] { background: var(--remarq-pending); }
702
- .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; }
703
869
 
704
870
  .remarq-popup {
705
871
  position: absolute;
@@ -744,14 +910,30 @@ var CSS = `
744
910
 
745
911
  .remarq-popup-actions {
746
912
  display: flex;
747
- justify-content: flex-end;
748
- gap: 8px;
913
+ flex-direction: column;
914
+ gap: 6px;
749
915
  padding: 8px 12px;
750
916
  border-top: 1px solid var(--remarq-border);
751
917
  }
752
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
+
753
935
  .remarq-popup-actions button {
754
- padding: 4px 12px;
936
+ padding: 5px 12px;
755
937
  border: 1px solid var(--remarq-border);
756
938
  border-radius: 4px;
757
939
  background: var(--remarq-bg);
@@ -766,6 +948,19 @@ var CSS = `
766
948
  color: #ffffff;
767
949
  }
768
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
+
769
964
  .remarq-detached-panel {
770
965
  position: fixed;
771
966
  z-index: 2147483646;
@@ -1024,6 +1219,51 @@ var CSS = `
1024
1219
  color: var(--remarq-text-secondary);
1025
1220
  margin-top: 4px;
1026
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
+ }
1027
1267
  `;
1028
1268
  function injectStyles() {
1029
1269
  if (document.querySelector(`style[${STYLES_ID}]`)) return;
@@ -1131,8 +1371,19 @@ var Toolbar = class {
1131
1371
  this.inspectBtn = this.createButton("inspect", ICONS.inspect, () => callbacks.onInspect());
1132
1372
  this.badgeEl = document.createElement("span");
1133
1373
  this.badgeEl.className = "remarq-badge";
1374
+ this.badgeEl.title = "Needs attention";
1134
1375
  this.badgeEl.style.display = "none";
1135
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);
1136
1387
  this.spacingBtn = this.createButton("spacing", ICONS.spacing, () => callbacks.onSpacingToggle());
1137
1388
  this.spacingBtn.disabled = true;
1138
1389
  const copyBtn = this.createButton("copy", ICONS.copy, () => callbacks.onCopy());
@@ -1179,6 +1430,10 @@ var Toolbar = class {
1179
1430
  this.badgeEl.textContent = String(count);
1180
1431
  this.badgeEl.style.display = count > 0 ? "flex" : "none";
1181
1432
  }
1433
+ setVerificationBadgeCount(count) {
1434
+ this.verificationBadgeEl.textContent = String(count);
1435
+ this.verificationBadgeEl.style.display = count > 0 ? "flex" : "none";
1436
+ }
1182
1437
  getFileInput() {
1183
1438
  return this.fileInput;
1184
1439
  }
@@ -1662,6 +1917,47 @@ var SpacingOverlay = class {
1662
1917
  };
1663
1918
 
1664
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
+ }
1665
1961
  var POPUP_WIDTH = 300;
1666
1962
  var POPUP_MARGIN = 8;
1667
1963
  var Popup = class {
@@ -1670,6 +1966,7 @@ var Popup = class {
1670
1966
  this.popupEl = null;
1671
1967
  this.keyHandler = null;
1672
1968
  this.outsideClickHandler = null;
1969
+ this.pendingEditFlush = null;
1673
1970
  }
1674
1971
  show(info, position, onSubmit, onCancel) {
1675
1972
  this.hide();
@@ -1754,7 +2051,7 @@ var Popup = class {
1754
2051
  popup2.className = "remarq-popup";
1755
2052
  const header = document.createElement("div");
1756
2053
  header.className = "remarq-popup-header";
1757
- header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${info.status}]`;
2054
+ header.textContent = `<${info.tag}>${info.text ? ` "${info.text}"` : ""} [${STATUS_LABEL[info.status]}]`;
1758
2055
  const body = document.createElement("div");
1759
2056
  body.className = "remarq-popup-body";
1760
2057
  const makeCommentEl = () => {
@@ -1766,38 +2063,10 @@ var Popup = class {
1766
2063
  return el;
1767
2064
  };
1768
2065
  body.appendChild(makeCommentEl());
2066
+ body.appendChild(this.buildLifecycleViewer(info.lifecycle));
1769
2067
  const actions = document.createElement("div");
1770
2068
  actions.className = "remarq-popup-actions";
1771
- if (info.status === "pending") {
1772
- const resolveBtn = document.createElement("button");
1773
- resolveBtn.className = "remarq-primary";
1774
- resolveBtn.textContent = "Resolve";
1775
- resolveBtn.addEventListener("click", () => {
1776
- this.hide();
1777
- callbacks.onResolve();
1778
- });
1779
- actions.appendChild(resolveBtn);
1780
- }
1781
- const copyBtn = document.createElement("button");
1782
- copyBtn.textContent = "Copy";
1783
- copyBtn.addEventListener("click", () => {
1784
- callbacks.onCopy();
1785
- });
1786
- actions.appendChild(copyBtn);
1787
- const deleteBtn = document.createElement("button");
1788
- deleteBtn.textContent = "Delete";
1789
- deleteBtn.addEventListener("click", () => {
1790
- this.hide();
1791
- callbacks.onDelete();
1792
- });
1793
- actions.appendChild(deleteBtn);
1794
- const closeBtn = document.createElement("button");
1795
- closeBtn.textContent = "Close";
1796
- closeBtn.addEventListener("click", () => {
1797
- this.hide();
1798
- callbacks.onClose();
1799
- });
1800
- actions.appendChild(closeBtn);
2069
+ this.renderActionButtons(actions, info, callbacks);
1801
2070
  popup2.appendChild(header);
1802
2071
  popup2.appendChild(body);
1803
2072
  popup2.appendChild(actions);
@@ -1824,7 +2093,103 @@ var Popup = class {
1824
2093
  document.addEventListener("mousedown", this.outsideClickHandler);
1825
2094
  }, 0);
1826
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
+ }
1827
2188
  hide() {
2189
+ if (this.pendingEditFlush) {
2190
+ this.pendingEditFlush();
2191
+ this.pendingEditFlush = null;
2192
+ }
1828
2193
  if (this.popupEl) {
1829
2194
  this.popupEl.remove();
1830
2195
  this.popupEl = null;
@@ -1859,12 +2224,8 @@ var Popup = class {
1859
2224
  commentEl.replaceWith(textarea);
1860
2225
  textarea.focus();
1861
2226
  textarea.selectionStart = textarea.value.length;
1862
- const saveEdit = () => {
1863
- const newComment = textarea.value.trim();
1864
- if (newComment && newComment !== info.comment) {
1865
- info.comment = newComment;
1866
- callbacks.onEdit(newComment);
1867
- }
2227
+ const restoreView = () => {
2228
+ if (!textarea.isConnected) return;
1868
2229
  const restored = document.createElement("div");
1869
2230
  restored.textContent = info.comment;
1870
2231
  restored.style.cursor = "pointer";
@@ -1872,26 +2233,33 @@ var Popup = class {
1872
2233
  restored.addEventListener("click", () => this.enterEditMode(restored, info, callbacks));
1873
2234
  textarea.replaceWith(restored);
1874
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;
1875
2250
  textarea.addEventListener("keydown", (e) => {
1876
2251
  if (e.key === "Enter" && !e.shiftKey) {
1877
2252
  e.preventDefault();
1878
- saveEdit();
2253
+ commitEdit();
1879
2254
  }
1880
2255
  if (e.key === "Escape") {
1881
2256
  e.stopPropagation();
1882
- const restored = document.createElement("div");
1883
- restored.textContent = info.comment;
1884
- restored.style.cursor = "pointer";
1885
- restored.title = "Click to edit";
1886
- restored.addEventListener("click", () => this.enterEditMode(restored, info, callbacks));
1887
- textarea.replaceWith(restored);
2257
+ cancelEdit();
1888
2258
  }
1889
2259
  });
1890
2260
  textarea.addEventListener("blur", () => {
1891
2261
  setTimeout(() => {
1892
- if (textarea.isConnected) {
1893
- saveEdit();
1894
- }
2262
+ if (textarea.isConnected) commitEdit();
1895
2263
  }, 50);
1896
2264
  });
1897
2265
  }
@@ -1919,6 +2287,16 @@ var Popup = class {
1919
2287
  };
1920
2288
 
1921
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
+ }
1922
2300
  var MarkerManager = class {
1923
2301
  constructor(container, onClick) {
1924
2302
  this.container = container;
@@ -1931,7 +2309,7 @@ var MarkerManager = class {
1931
2309
  addMarker(annotation, target) {
1932
2310
  this.counter++;
1933
2311
  const markerEl = document.createElement("div");
1934
- markerEl.className = "remarq-marker";
2312
+ markerEl.className = `remarq-marker ${statusClass(annotation.status)}`;
1935
2313
  markerEl.setAttribute("data-status", annotation.status);
1936
2314
  markerEl.setAttribute("data-annotation-id", annotation.id);
1937
2315
  markerEl.textContent = String(this.counter);
@@ -1957,7 +2335,17 @@ var MarkerManager = class {
1957
2335
  const entry = this.markers.get(id);
1958
2336
  if (entry) {
1959
2337
  entry.annotation.status = status;
2338
+ entry.markerEl.className = `remarq-marker ${statusClass(status)}`;
1960
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) {
1961
2349
  }
1962
2350
  }
1963
2351
  clear() {
@@ -1976,7 +2364,7 @@ var MarkerManager = class {
1976
2364
  this.clear();
1977
2365
  }
1978
2366
  applyOutline(target, status) {
1979
- const color = status === "pending" ? "#f97316" : "rgba(34, 197, 94, 0.5)";
2367
+ const color = STATUS_COLOR[status];
1980
2368
  target.style.outline = `2px solid ${color}`;
1981
2369
  target.style.outlineOffset = "2px";
1982
2370
  }
@@ -2323,8 +2711,18 @@ function refreshMarkers() {
2323
2711
  markers.addMarker(ann, el);
2324
2712
  }
2325
2713
  detachedPanel.update(otherBreakpoint, detached);
2326
- const pendingCount = anns.filter((a) => a.status === "pending").length;
2327
- 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);
2328
2726
  }
2329
2727
  function scheduleRefresh() {
2330
2728
  if (refreshScheduled) return;
@@ -2357,6 +2755,7 @@ function handleInspectClick(e) {
2357
2755
  classFilter: options.classFilter,
2358
2756
  dataAttribute: options.dataAttribute
2359
2757
  });
2758
+ const now = Date.now();
2360
2759
  const ann = {
2361
2760
  id: generateId(),
2362
2761
  comment,
@@ -2364,8 +2763,9 @@ function handleInspectClick(e) {
2364
2763
  route: currentRoute(),
2365
2764
  viewport: `${window.innerWidth}x${window.innerHeight}`,
2366
2765
  viewportBucket: toBucket(window.innerWidth),
2367
- timestamp: Date.now(),
2368
- status: "pending"
2766
+ timestamp: now,
2767
+ status: "pending",
2768
+ lifecycle: [{ type: "created", actor: "designer", timestamp: now }]
2369
2769
  };
2370
2770
  cacheElement(ann.id, target);
2371
2771
  storage.add(ann);
@@ -2457,7 +2857,8 @@ function handleMarkerClick(annotationId) {
2457
2857
  tag: ann.fingerprint.tagName,
2458
2858
  text: (_a3 = ann.fingerprint.textContent) != null ? _a3 : "",
2459
2859
  comment: ann.comment,
2460
- status: ann.status
2860
+ status: ann.status,
2861
+ lifecycle: ann.lifecycle
2461
2862
  },
2462
2863
  {
2463
2864
  top: window.scrollY + rect.bottom + 8,
@@ -2465,9 +2866,8 @@ function handleMarkerClick(annotationId) {
2465
2866
  anchorBottom: window.scrollY + rect.top - 8
2466
2867
  },
2467
2868
  {
2468
- onResolve: () => {
2469
- storage.update(ann.id, { status: "resolved" });
2470
- refreshMarkers();
2869
+ onTransition: (action, reason) => {
2870
+ applyTransition(ann.id, action, reason ? { reason } : void 0);
2471
2871
  },
2472
2872
  onDelete: () => {
2473
2873
  elementCache.delete(ann.id);
@@ -2481,12 +2881,14 @@ function handleMarkerClick(annotationId) {
2481
2881
  refreshMarkers();
2482
2882
  },
2483
2883
  onCopy: () => {
2484
- const fp = ann.fingerprint;
2884
+ var _a4;
2885
+ const fresh = (_a4 = storage.getById(ann.id)) != null ? _a4 : ann;
2886
+ const fp = fresh.fingerprint;
2485
2887
  const lines = [
2486
- `[${ann.status}] "${ann.comment}"`,
2888
+ `[${fresh.status}] "${fresh.comment}"`,
2487
2889
  `Element: <${fp.tagName}>${fp.textContent ? ` "${fp.textContent}"` : ""}`,
2488
- `Route: ${ann.route}`,
2489
- `Viewport: ${ann.viewportBucket}px`
2890
+ `Route: ${fresh.route}`,
2891
+ `Viewport: ${fresh.viewportBucket}px`
2490
2892
  ];
2491
2893
  if (fp.sourceLocation) lines.push(`Source: ${fp.sourceLocation}`);
2492
2894
  navigator.clipboard.writeText(lines.join("\n")).then(() => {
@@ -2621,6 +3023,16 @@ function copyAgentToClipboard() {
2621
3023
  console.warn("[web-remarq] Clipboard write failed");
2622
3024
  });
2623
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
+ }
2624
3036
  function setupMutationObserver() {
2625
3037
  mutationObserver = new MutationObserver((mutations) => {
2626
3038
  let hasExternalMutation = false;
@@ -2640,18 +3052,18 @@ function setupMutationObserver() {
2640
3052
  }
2641
3053
  var WebRemarq = {
2642
3054
  init(opts) {
2643
- var _a3;
3055
+ var _a3, _b;
2644
3056
  if (initialized) return;
2645
3057
  options = opts != null ? opts : {};
2646
3058
  try {
2647
3059
  injectStyles();
2648
- storage = new AnnotationStorage();
3060
+ storage = new AnnotationStorage((_a3 = options.storage) != null ? _a3 : new LocalStorageAdapter());
2649
3061
  themeManager = new ThemeManager(document.body, options.theme);
2650
3062
  overlay = new Overlay(themeManager.container);
2651
3063
  spacingOverlay = new SpacingOverlay(themeManager.container);
2652
3064
  popup = new Popup(themeManager.container);
2653
3065
  markers = new MarkerManager(themeManager.container, handleMarkerClick);
2654
- const position = (_a3 = options.position) != null ? _a3 : "bottom-right";
3066
+ const position = (_b = options.position) != null ? _b : "bottom-right";
2655
3067
  detachedPanel = new DetachedPanel(themeManager.container, (id) => {
2656
3068
  elementCache.delete(id);
2657
3069
  storage.remove(id);
@@ -2682,11 +3094,9 @@ var WebRemarq = {
2682
3094
  showToast(themeManager.container, "All annotations cleared");
2683
3095
  },
2684
3096
  onThemeToggle: () => themeManager.toggle(),
2685
- onHelp: () => showShortcutsModal(themeManager.container)
3097
+ onHelp: () => showShortcutsModal(themeManager.container),
3098
+ onVerificationBadgeClick: jumpToFirstUnverified
2686
3099
  }, position);
2687
- if (storage.isMemoryOnly) {
2688
- toolbar.setMemoryWarning(true);
2689
- }
2690
3100
  routeObserver = new RouteObserver();
2691
3101
  unsubRoute = routeObserver.onChange(() => refreshMarkers());
2692
3102
  document.addEventListener("click", handleInspectClick, true);
@@ -2694,8 +3104,13 @@ var WebRemarq = {
2694
3104
  document.addEventListener("keydown", handleInspectKeydown);
2695
3105
  setupMutationObserver();
2696
3106
  initViewportListener(() => refreshMarkers());
3107
+ storage.ready.then(() => {
3108
+ if (storage.isMemoryOnly) {
3109
+ toolbar.setMemoryWarning(true);
3110
+ }
3111
+ refreshMarkers();
3112
+ });
2697
3113
  console.debug(`[web-remarq] Initialized on route: ${currentRoute()}`);
2698
- refreshMarkers();
2699
3114
  initialized = true;
2700
3115
  } catch (err) {
2701
3116
  console.error("[web-remarq] Init failed:", err);
@@ -2774,10 +3189,33 @@ var WebRemarq = {
2774
3189
  elementCache.clear();
2775
3190
  storage == null ? void 0 : storage.clearAll();
2776
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");
2777
3214
  }
2778
3215
  };
2779
3216
  // Annotate the CommonJS export names for ESM import in node:
2780
3217
  0 && (module.exports = {
3218
+ LocalStorageAdapter,
2781
3219
  WebRemarq
2782
3220
  });
2783
3221
  //# sourceMappingURL=index.cjs.map