@unhingged/vizu-core 0.1.22 → 0.2.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
@@ -9,9 +9,9 @@ var __export = (target, all) => {
9
9
  };
10
10
  var __copyProps = (to, from, except, desc) => {
11
11
  if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key2 of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key2) && key2 !== except)
14
- __defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
15
  }
16
16
  return to;
17
17
  };
@@ -21,9 +21,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
23
  CloudStorageAdapter: () => CloudStorageAdapter,
24
- InMemoryStorageAdapter: () => InMemoryStorageAdapter,
25
- LocalStorageAdapter: () => LocalStorageAdapter,
26
- NullStorageAdapter: () => NullStorageAdapter,
27
24
  SCHEMA_VERSION: () => SCHEMA_VERSION,
28
25
  Vizu: () => Vizu,
29
26
  findByFingerprint: () => findByFingerprint,
@@ -168,204 +165,6 @@ function needsPersist(raws) {
168
165
  return false;
169
166
  }
170
167
 
171
- // src/storage.ts
172
- var key = (ns) => `vizu:comments:${ns}`;
173
- var LocalStorageAdapter = class {
174
- async load(namespace) {
175
- return readArray(namespace);
176
- }
177
- async addComment(namespace, comment) {
178
- const list = readArray(namespace);
179
- list.push(comment);
180
- writeArray(namespace, list);
181
- }
182
- async updateComment(namespace, id, patch) {
183
- const list = readArray(namespace);
184
- const idx = list.findIndex((c) => c.id === id);
185
- if (idx === -1) return null;
186
- const next = { ...list[idx], ...patch, id: list[idx].id };
187
- list[idx] = next;
188
- writeArray(namespace, list);
189
- return next;
190
- }
191
- async removeComment(namespace, id) {
192
- const list = readArray(namespace);
193
- writeArray(namespace, list.filter((c) => c.id !== id));
194
- }
195
- async setAll(namespace, comments) {
196
- writeArray(namespace, comments);
197
- }
198
- async clear(namespace) {
199
- if (typeof localStorage === "undefined") return;
200
- localStorage.removeItem(key(namespace));
201
- }
202
- async addReply(namespace, commentId, reply) {
203
- const list = readArray(namespace);
204
- const idx = list.findIndex((c) => c.id === commentId);
205
- if (idx === -1) return null;
206
- const next = { ...list[idx], replies: [...list[idx].replies ?? [], reply] };
207
- list[idx] = next;
208
- writeArray(namespace, list);
209
- return next;
210
- }
211
- async removeReply(namespace, commentId, replyId) {
212
- const list = readArray(namespace);
213
- const idx = list.findIndex((c) => c.id === commentId);
214
- if (idx === -1) return null;
215
- const next = {
216
- ...list[idx],
217
- replies: (list[idx].replies ?? []).filter((r) => r.id !== replyId)
218
- };
219
- list[idx] = next;
220
- writeArray(namespace, list);
221
- return next;
222
- }
223
- };
224
- function readArray(namespace) {
225
- if (typeof localStorage === "undefined") return [];
226
- const raw = localStorage.getItem(key(namespace));
227
- if (!raw) return [];
228
- try {
229
- const parsed = JSON.parse(raw);
230
- return Array.isArray(parsed) ? migrateComments(parsed) : [];
231
- } catch {
232
- return [];
233
- }
234
- }
235
- function writeArray(namespace, comments) {
236
- if (typeof localStorage === "undefined") return;
237
- localStorage.setItem(key(namespace), JSON.stringify(comments));
238
- }
239
- var InMemoryStorageAdapter = class {
240
- constructor() {
241
- this.store = /* @__PURE__ */ new Map();
242
- }
243
- async load(namespace) {
244
- return [...this.store.get(namespace) || []];
245
- }
246
- async addComment(namespace, comment) {
247
- const list = this.store.get(namespace) ?? [];
248
- this.store.set(namespace, [...list, comment]);
249
- }
250
- async updateComment(namespace, id, patch) {
251
- const list = this.store.get(namespace) ?? [];
252
- const idx = list.findIndex((c) => c.id === id);
253
- if (idx === -1) return null;
254
- const next = { ...list[idx], ...patch, id: list[idx].id };
255
- const copy = [...list];
256
- copy[idx] = next;
257
- this.store.set(namespace, copy);
258
- return next;
259
- }
260
- async removeComment(namespace, id) {
261
- const list = this.store.get(namespace) ?? [];
262
- this.store.set(
263
- namespace,
264
- list.filter((c) => c.id !== id)
265
- );
266
- }
267
- async setAll(namespace, comments) {
268
- this.store.set(namespace, [...comments]);
269
- }
270
- async clear(namespace) {
271
- this.store.delete(namespace);
272
- }
273
- async addReply(namespace, commentId, reply) {
274
- const list = this.store.get(namespace) ?? [];
275
- const idx = list.findIndex((c) => c.id === commentId);
276
- if (idx === -1) return null;
277
- const next = { ...list[idx], replies: [...list[idx].replies ?? [], reply] };
278
- const copy = [...list];
279
- copy[idx] = next;
280
- this.store.set(namespace, copy);
281
- return next;
282
- }
283
- async removeReply(namespace, commentId, replyId) {
284
- const list = this.store.get(namespace) ?? [];
285
- const idx = list.findIndex((c) => c.id === commentId);
286
- if (idx === -1) return null;
287
- const next = {
288
- ...list[idx],
289
- replies: (list[idx].replies ?? []).filter((r) => r.id !== replyId)
290
- };
291
- const copy = [...list];
292
- copy[idx] = next;
293
- this.store.set(namespace, copy);
294
- return next;
295
- }
296
- };
297
- var NullStorageAdapter = class {
298
- async load() {
299
- return [];
300
- }
301
- async addComment() {
302
- }
303
- async updateComment() {
304
- return null;
305
- }
306
- async removeComment() {
307
- }
308
- async setAll() {
309
- }
310
- async clear() {
311
- }
312
- };
313
- function isV1Adapter(x) {
314
- if (!x || typeof x !== "object") return false;
315
- const o = x;
316
- return typeof o.load === "function" && typeof o.save === "function" && typeof o.clear === "function" && typeof o.addComment !== "function";
317
- }
318
- function wrapV1Adapter(v1) {
319
- if (!isV1Adapter(v1)) return v1;
320
- let warned = false;
321
- const warnOnce = () => {
322
- if (warned) return;
323
- warned = true;
324
- if (typeof console !== "undefined") {
325
- console.warn(
326
- "[vizu] StorageAdapter v1 detected. Per-comment ops are emulated via full-array rewrites \u2014 upgrade your adapter to the v2 interface (addComment / updateComment / removeComment / setAll / load / clear) when you can."
327
- );
328
- }
329
- };
330
- return {
331
- async load(namespace) {
332
- warnOnce();
333
- return v1.load(namespace);
334
- },
335
- async addComment(namespace, comment) {
336
- warnOnce();
337
- const list = await v1.load(namespace);
338
- list.push(comment);
339
- await v1.save(namespace, list);
340
- },
341
- async updateComment(namespace, id, patch) {
342
- warnOnce();
343
- const list = await v1.load(namespace);
344
- const idx = list.findIndex((c) => c.id === id);
345
- if (idx === -1) return null;
346
- const next = { ...list[idx], ...patch, id: list[idx].id };
347
- list[idx] = next;
348
- await v1.save(namespace, list);
349
- return next;
350
- },
351
- async removeComment(namespace, id) {
352
- warnOnce();
353
- const list = await v1.load(namespace);
354
- await v1.save(
355
- namespace,
356
- list.filter((c) => c.id !== id)
357
- );
358
- },
359
- async setAll(namespace, comments) {
360
- warnOnce();
361
- await v1.save(namespace, comments);
362
- },
363
- async clear(namespace) {
364
- await v1.clear(namespace);
365
- }
366
- };
367
- }
368
-
369
168
  // src/cloud.ts
370
169
  var DEFAULT_API_URL = "https://vizu.unhingged.com";
371
170
  var POPUP_WIDTH = 480;
@@ -1422,8 +1221,8 @@ var Popover = class {
1422
1221
  const removeBtn = target.closest('[data-vz="remove-anchor"]');
1423
1222
  if (removeBtn) {
1424
1223
  e.stopPropagation();
1425
- const key2 = removeBtn.getAttribute("data-fp-key");
1426
- if (key2) this.removeAnchor(key2);
1224
+ const key = removeBtn.getAttribute("data-fp-key");
1225
+ if (key) this.removeAnchor(key);
1427
1226
  return;
1428
1227
  }
1429
1228
  const refEl = target.closest('[data-vz="ref"]');
@@ -1518,8 +1317,8 @@ var Popover = class {
1518
1317
  }
1519
1318
  /** Add another anchor to the in-progress comment (Shift+Click flow). */
1520
1319
  addAnchor(fp, target) {
1521
- const key2 = fingerprintKey(fp);
1522
- if (this.anchorFingerprints.some((x) => fingerprintKey(x) === key2)) return false;
1320
+ const key = fingerprintKey(fp);
1321
+ if (this.anchorFingerprints.some((x) => fingerprintKey(x) === key)) return false;
1523
1322
  this.anchorFingerprints.push(fp);
1524
1323
  if (target) this.anchorTargets.push(target);
1525
1324
  this.callbacks.onAnchorsChanged([...this.anchorFingerprints]);
@@ -1528,9 +1327,9 @@ var Popover = class {
1528
1327
  return true;
1529
1328
  }
1530
1329
  /** Remove an anchor by fingerprint key. Never removes the last anchor. */
1531
- removeAnchor(key2) {
1330
+ removeAnchor(key) {
1532
1331
  if (this.anchorFingerprints.length <= 1) return false;
1533
- const idx = this.anchorFingerprints.findIndex((x) => fingerprintKey(x) === key2);
1332
+ const idx = this.anchorFingerprints.findIndex((x) => fingerprintKey(x) === key);
1534
1333
  if (idx < 0) return false;
1535
1334
  this.anchorFingerprints.splice(idx, 1);
1536
1335
  this.anchorTargets.splice(idx, 1);
@@ -1640,9 +1439,9 @@ var Popover = class {
1640
1439
  return;
1641
1440
  }
1642
1441
  const chips = this.anchorFingerprints.map((fp) => {
1643
- const key2 = fingerprintKey(fp);
1442
+ const key = fingerprintKey(fp);
1644
1443
  const label = fingerprintLabel(fp);
1645
- return `<span class="vz-anchor-chip" data-fp-key="${escapeHtml(key2)}">${escapeHtml(label)}<button class="vz-anchor-chip-remove" data-vz="remove-anchor" data-fp-key="${escapeHtml(key2)}" aria-label="Remove anchor">\xD7</button></span>`;
1444
+ return `<span class="vz-anchor-chip" data-fp-key="${escapeHtml(key)}">${escapeHtml(label)}<button class="vz-anchor-chip-remove" data-vz="remove-anchor" data-fp-key="${escapeHtml(key)}" aria-label="Remove anchor">\xD7</button></span>`;
1646
1445
  }).join("");
1647
1446
  wrap.innerHTML = `
1648
1447
  <div class="vz-anchor-hint">Anchored to ${this.anchorFingerprints.length} elements:</div>
@@ -2369,10 +2168,10 @@ var Sidebar = class {
2369
2168
  const fps = c.fingerprints || [];
2370
2169
  const primary = fps[0];
2371
2170
  if (!primary) continue;
2372
- const key2 = fingerprintKey(primary);
2373
- const g = groups.get(key2);
2171
+ const key = fingerprintKey(primary);
2172
+ const g = groups.get(key);
2374
2173
  if (g) g.comments.push(c);
2375
- else groups.set(key2, { fp: primary, comments: [c] });
2174
+ else groups.set(key, { fp: primary, comments: [c] });
2376
2175
  }
2377
2176
  const groupHtml = [];
2378
2177
  let gi = 0;
@@ -2433,8 +2232,8 @@ var Sidebar = class {
2433
2232
  }
2434
2233
  const chips = this.el.querySelectorAll('[data-vz="jump-fp"]');
2435
2234
  for (const chip of chips) {
2436
- const key2 = chip.getAttribute("data-fp-key");
2437
- const fp = findFingerprintByKey(visible, key2 || "");
2235
+ const key = chip.getAttribute("data-fp-key");
2236
+ const fp = findFingerprintByKey(visible, key || "");
2438
2237
  chip.__fp = fp;
2439
2238
  }
2440
2239
  }
@@ -2522,10 +2321,10 @@ function renderOrphansHtml(orphans) {
2522
2321
  </div>
2523
2322
  `;
2524
2323
  }
2525
- function findFingerprintByKey(comments, key2) {
2324
+ function findFingerprintByKey(comments, key) {
2526
2325
  for (const c of comments) {
2527
2326
  for (const fp of c.fingerprints || []) {
2528
- if (fingerprintKey(fp) === key2) return fp;
2327
+ if (fingerprintKey(fp) === key) return fp;
2529
2328
  }
2530
2329
  }
2531
2330
  return null;
@@ -3620,30 +3419,17 @@ var EventBus = class {
3620
3419
  // src/index.ts
3621
3420
  var DEFAULTS = {
3622
3421
  shortcut: "mod+shift+e",
3623
- namespace: "default",
3624
3422
  accent: "#FF6647"
3625
3423
  };
3626
- function resolveStorage(storage, cloud, defaultMode, onAuthChanged) {
3627
- if (cloud) {
3628
- if (storage && typeof console !== "undefined") {
3629
- console.warn("[vizu] Both `cloud` and `storage` set; `cloud` wins. Drop `storage` to silence this.");
3630
- }
3631
- return new CloudStorageAdapter({
3632
- workspace: cloud.workspace,
3633
- apiUrl: cloud.apiUrl,
3634
- autoSignIn: cloud.autoSignIn,
3635
- onAuthChanged
3636
- });
3637
- }
3638
- if (!storage) return defaultMode === "local" ? new LocalStorageAdapter() : new InMemoryStorageAdapter();
3639
- if (storage === "local") return new LocalStorageAdapter();
3640
- if (storage === "memory") return new InMemoryStorageAdapter();
3641
- if (storage === "none") return new NullStorageAdapter();
3642
- if (isV1Adapter(storage)) return wrapV1Adapter(storage);
3643
- return storage;
3644
- }
3645
3424
  var Vizu = class {
3646
- constructor(options = {}, _defaultStorage = "memory") {
3425
+ constructor(options, storageOverride) {
3426
+ /**
3427
+ * The cloud adapter when this instance runs in the normal cloud mode;
3428
+ * null only under the internal storage override (tests / storybook).
3429
+ * Auth-gated behavior (mount gate, write gate, sign-in kicks) branches
3430
+ * on this instead of `instanceof` checks against `this.storage`.
3431
+ */
3432
+ this.cloud = null;
3647
3433
  this.root = null;
3648
3434
  this.highlighter = null;
3649
3435
  this.popover = null;
@@ -3705,8 +3491,8 @@ var Vizu = class {
3705
3491
  this.pendingTargets = [];
3706
3492
  /**
3707
3493
  * Subscribe to remote-change notifications from the active storage
3708
- * adapter (cloud adapters only). Local adapters never emit. The
3709
- * subscription is torn down on `destroy()`.
3494
+ * adapter, when it implements `subscribe` (none do today — kept as
3495
+ * the hook realtime lands on). Torn down on `destroy()`.
3710
3496
  */
3711
3497
  this.unsubscribeStorage = null;
3712
3498
  this.onKeyDown = (e) => {
@@ -3749,23 +3535,34 @@ var Vizu = class {
3749
3535
  this.popover?.reposition();
3750
3536
  });
3751
3537
  };
3538
+ if (!storageOverride && !options?.workspace) {
3539
+ throw new Error(
3540
+ "[vizu] `workspace` is required \u2014 create one at https://vizu.unhingged.com and pass new Vizu({ workspace: 'your-slug' })."
3541
+ );
3542
+ }
3752
3543
  this.opts = { ...options };
3753
3544
  this.opts.shortcut = options.shortcut ?? DEFAULTS.shortcut;
3754
- this.opts.namespace = options.namespace ?? DEFAULTS.namespace;
3755
3545
  this.opts.accent = options.accent ?? DEFAULTS.accent;
3546
+ this.ns = options.workspace ?? "default";
3756
3547
  this.parsedShortcut = parseShortcut(this.opts.shortcut);
3757
3548
  this.user = options.user ?? null;
3758
- this.storage = resolveStorage(
3759
- options.storage,
3760
- options.cloud,
3761
- _defaultStorage,
3762
- (info) => {
3763
- if (info.user) this.setUser(info.user);
3764
- if (this.hasLoadedComments) {
3765
- void this.loadComments();
3549
+ if (storageOverride) {
3550
+ this.storage = storageOverride;
3551
+ this.cloud = null;
3552
+ } else {
3553
+ this.cloud = new CloudStorageAdapter({
3554
+ workspace: options.workspace,
3555
+ apiUrl: options.apiUrl,
3556
+ autoSignIn: options.autoSignIn,
3557
+ onAuthChanged: (info) => {
3558
+ if (info.user) this.setUser(info.user);
3559
+ if (this.hasLoadedComments) {
3560
+ void this.loadComments();
3561
+ }
3766
3562
  }
3767
- }
3768
- );
3563
+ });
3564
+ this.storage = this.cloud;
3565
+ }
3769
3566
  if (options.actions) this.actions = [...options.actions];
3770
3567
  if (options.onCommentAdded) this.bus.on("comment:added", (p) => options.onCommentAdded(p.comment));
3771
3568
  if (options.onCommentRemoved) this.bus.on("comment:removed", (p) => options.onCommentRemoved(p.id));
@@ -3807,8 +3604,8 @@ var Vizu = class {
3807
3604
  * Resolution order:
3808
3605
  * 1. `options.mentionable` if provided — host-supplied static or
3809
3606
  * custom resolver. Wins over everything else.
3810
- * 2. Cloud adapter's API call if running cloud mode.
3811
- * 3. Empty array (non-cloud, no override).
3607
+ * 2. The workspace's mentionable-members API.
3608
+ * 3. Empty array (internal storage override, no override).
3812
3609
  */
3813
3610
  async searchMentionable() {
3814
3611
  if (this.opts.mentionable) {
@@ -3821,38 +3618,36 @@ var Vizu = class {
3821
3618
  return [];
3822
3619
  }
3823
3620
  }
3824
- if (this.storage instanceof CloudStorageAdapter) {
3825
- return this.storage.searchMentionable();
3826
- }
3621
+ if (this.cloud) return this.cloud.searchMentionable();
3827
3622
  return [];
3828
3623
  }
3829
3624
  /**
3830
3625
  * Whether the full Vizu UI (pill, highlighter, markers, popover) can
3831
- * be mounted right now. Cloud-mode workspaces hold off until the
3832
- * user is signed in; non-cloud usage (local / memory storage) has no
3833
- * auth concept and mounts immediately. Re-checked from `enable()`
3834
- * and from `setUser()` so that the moment auth resolves, the UI
3835
- * appears without the user needing to press the shortcut again.
3626
+ * be mounted right now: not until the user is signed in to the
3627
+ * workspace. Re-checked from `enable()` and from `setUser()` so that
3628
+ * the moment auth resolves, the UI appears without the user needing
3629
+ * to press the shortcut again. (Under the internal storage override
3630
+ * there is no auth concept and the UI mounts immediately.)
3836
3631
  */
3837
3632
  shouldMount() {
3838
- if (!(this.storage instanceof CloudStorageAdapter)) return true;
3633
+ if (!this.cloud) return true;
3839
3634
  return this.user !== null;
3840
3635
  }
3841
3636
  /**
3842
- * Cloud-mode write gate. Every entry point that creates or modifies a
3843
- * comment first checks that we have a known user. Without one, the
3844
- * write would land as Anonymous (or 401 on the backend) — neither is
3845
- * the right UX. So we re-trigger the sign-in popup and refuse the
3846
- * action. The user signs in, this.user gets set via onAuthChanged,
3847
- * then they can retry the click themselves.
3637
+ * Write gate. Every entry point that creates or modifies a comment
3638
+ * first checks that we have a known user. Without one, the write
3639
+ * would land as Anonymous (or 401 on the backend) — neither is the
3640
+ * right UX. So we re-trigger the sign-in popup and refuse the action.
3641
+ * The user signs in, this.user gets set via onAuthChanged, then they
3642
+ * can retry the click themselves.
3848
3643
  *
3849
3644
  * Returns true when the caller may proceed.
3850
3645
  */
3851
3646
  requireUserOrAuth() {
3852
- if (!(this.storage instanceof CloudStorageAdapter)) return true;
3647
+ if (!this.cloud) return true;
3853
3648
  if (this.user) return true;
3854
- if (this.storage.isAccessDenied()) return false;
3855
- void this.storage.preflightAuth().catch(() => {
3649
+ if (this.cloud.isAccessDenied()) return false;
3650
+ void this.cloud.preflightAuth().catch(() => {
3856
3651
  });
3857
3652
  return false;
3858
3653
  }
@@ -3879,19 +3674,19 @@ var Vizu = class {
3879
3674
  return this.enabled;
3880
3675
  }
3881
3676
  /**
3882
- * Kick the cloud sign-in flow. Call this alongside `enable()` from an
3677
+ * Kick the sign-in flow. Call this alongside `enable()` from an
3883
3678
  * explicit user gesture — a host-rendered launcher button, a menu item —
3884
- * so cloud-mode users get the sign-in popup immediately instead of only
3885
- * after clicking an element. The keyboard shortcut goes through this
3886
- * same path. No-op outside cloud mode, when already signed in, or when
3887
- * the workspace has already denied access this session.
3679
+ * so users get the sign-in popup immediately instead of only after
3680
+ * clicking an element. The keyboard shortcut goes through this same
3681
+ * path. No-op when already signed in, when the workspace has already
3682
+ * denied access this session, or under the internal storage override.
3888
3683
  */
3889
3684
  requestAuth() {
3890
- if (!(this.storage instanceof CloudStorageAdapter)) return;
3685
+ if (!this.cloud) return;
3891
3686
  if (this.user) return;
3892
- if (this.storage.isAccessDenied()) return;
3893
- void this.storage.preflightAuth().catch((err) => {
3894
- if (err?.code === "auth_canceled" || err?.code === "popup_blocked") return;
3687
+ if (this.cloud.isAccessDenied()) return;
3688
+ void this.cloud.preflightAuth().catch((err) => {
3689
+ if (err?.code === "auth_canceled" || err?.code === "popup_blocked" || err?.code === "auth_required") return;
3895
3690
  if (typeof console !== "undefined") {
3896
3691
  console.warn("[vizu] preflight auth failed:", err);
3897
3692
  }
@@ -3932,7 +3727,7 @@ var Vizu = class {
3932
3727
  }
3933
3728
  async setComments(comments, opts) {
3934
3729
  this.comments = migrateComments(comments);
3935
- if (opts?.persist !== false) await this.storage.setAll(this.opts.namespace, this.comments);
3730
+ if (opts?.persist !== false) await this.storage.setAll(this.ns, this.comments);
3936
3731
  this.bus.emit("comments:set", { comments: this.getComments() });
3937
3732
  this.refreshUi();
3938
3733
  }
@@ -3947,7 +3742,7 @@ var Vizu = class {
3947
3742
  const { id: _i, schemaVersion: _s, ...safe } = patch;
3948
3743
  const next = { ...this.comments[idx], ...safe };
3949
3744
  this.comments[idx] = next;
3950
- const persisted = await this.storage.updateComment(this.opts.namespace, id, safe);
3745
+ const persisted = await this.storage.updateComment(this.ns, id, safe);
3951
3746
  this.bus.emit("comment:updated", { comment: persisted ?? next });
3952
3747
  this.refreshUi();
3953
3748
  return next;
@@ -3989,7 +3784,7 @@ var Vizu = class {
3989
3784
  const idx = this.comments.findIndex((x) => x.id === c.id);
3990
3785
  if (idx !== -1) this.comments[idx] = { ...this.comments[idx], ...patch };
3991
3786
  try {
3992
- await this.storage.updateComment(this.opts.namespace, c.id, patch);
3787
+ await this.storage.updateComment(this.ns, c.id, patch);
3993
3788
  } catch (err) {
3994
3789
  if (typeof console !== "undefined") {
3995
3790
  console.warn("[vizu] self-heal write failed for", c.id, err);
@@ -4011,8 +3806,8 @@ var Vizu = class {
4011
3806
  }
4012
3807
  /**
4013
3808
  * True iff the active storage adapter implements `uploadAttachment`.
4014
- * The popover hides its upload UI when this returns false (local /
4015
- * memory modes don't support attachments today).
3809
+ * The popover hides its upload UI when this returns false (only the
3810
+ * internal offline adapters lack uploads).
4016
3811
  */
4017
3812
  canUploadAttachments() {
4018
3813
  return typeof this.storage.uploadAttachment === "function";
@@ -4025,10 +3820,10 @@ var Vizu = class {
4025
3820
  async uploadAttachment(file) {
4026
3821
  if (!this.storage.uploadAttachment) {
4027
3822
  throw new Error(
4028
- "[vizu] active storage adapter does not support uploadAttachment; only the CloudStorageAdapter does in v1"
3823
+ "[vizu] active storage adapter does not support uploadAttachment"
4029
3824
  );
4030
3825
  }
4031
- return this.storage.uploadAttachment(this.opts.namespace, file);
3826
+ return this.storage.uploadAttachment(this.ns, file);
4032
3827
  }
4033
3828
  /**
4034
3829
  * Append a reply to a comment. Returns the parent comment or null if
@@ -4056,9 +3851,9 @@ var Vizu = class {
4056
3851
  this.comments[idx] = updated;
4057
3852
  try {
4058
3853
  if (this.storage.addReply) {
4059
- await this.storage.addReply(this.opts.namespace, commentId, reply);
3854
+ await this.storage.addReply(this.ns, commentId, reply);
4060
3855
  } else {
4061
- await this.storage.updateComment(this.opts.namespace, commentId, { replies: updated.replies });
3856
+ await this.storage.updateComment(this.ns, commentId, { replies: updated.replies });
4062
3857
  }
4063
3858
  } catch (err) {
4064
3859
  const cur = this.comments[idx];
@@ -4098,9 +3893,9 @@ var Vizu = class {
4098
3893
  const updated = { ...this.comments[idx], replies: filtered };
4099
3894
  this.comments[idx] = updated;
4100
3895
  if (this.storage.removeReply) {
4101
- await this.storage.removeReply(this.opts.namespace, commentId, replyId);
3896
+ await this.storage.removeReply(this.ns, commentId, replyId);
4102
3897
  } else {
4103
- await this.storage.updateComment(this.opts.namespace, commentId, { replies: filtered });
3898
+ await this.storage.updateComment(this.ns, commentId, { replies: filtered });
4104
3899
  }
4105
3900
  this.bus.emit("comment:updated", { comment: updated });
4106
3901
  this.refreshUi();
@@ -4113,7 +3908,7 @@ var Vizu = class {
4113
3908
  async clearAll() {
4114
3909
  const previous = this.comments;
4115
3910
  this.comments = [];
4116
- await this.storage.clear(this.opts.namespace);
3911
+ await this.storage.clear(this.ns);
4117
3912
  this.bus.emit("comments:cleared", { previous });
4118
3913
  this.refreshUi();
4119
3914
  this.popover?.close();
@@ -4154,10 +3949,10 @@ var Vizu = class {
4154
3949
  }
4155
3950
  /* ===== Internal ===== */
4156
3951
  async loadComments() {
4157
- const raw = await this.storage.load(this.opts.namespace);
3952
+ const raw = await this.storage.load(this.ns);
4158
3953
  this.comments = migrateComments(raw);
4159
3954
  if (needsPersist(raw)) {
4160
- void this.storage.setAll(this.opts.namespace, this.comments).catch(() => {
3955
+ void this.storage.setAll(this.ns, this.comments).catch(() => {
4161
3956
  });
4162
3957
  }
4163
3958
  this.bus.emit("comments:loaded", { comments: this.getComments() });
@@ -4166,7 +3961,7 @@ var Vizu = class {
4166
3961
  subscribeStorage() {
4167
3962
  if (this.unsubscribeStorage) return;
4168
3963
  if (!this.storage.subscribe) return;
4169
- this.unsubscribeStorage = this.storage.subscribe(this.opts.namespace, (e) => {
3964
+ this.unsubscribeStorage = this.storage.subscribe(this.ns, (e) => {
4170
3965
  if (e.type === "added") {
4171
3966
  const fresh = migrateComment(e.comment);
4172
3967
  if (!this.comments.some((c) => c.id === fresh.id)) {
@@ -4318,9 +4113,9 @@ var Vizu = class {
4318
4113
  this.bus.emit("element:deselected", {});
4319
4114
  }
4320
4115
  addToSelection(fp, el) {
4321
- const key2 = fingerprintKey(fp);
4322
- if (this.pendingFingerprints.some((x) => fingerprintKey(x) === key2)) {
4323
- this.pendingFingerprints = this.pendingFingerprints.filter((x) => fingerprintKey(x) !== key2);
4116
+ const key = fingerprintKey(fp);
4117
+ if (this.pendingFingerprints.some((x) => fingerprintKey(x) === key)) {
4118
+ this.pendingFingerprints = this.pendingFingerprints.filter((x) => fingerprintKey(x) !== key);
4324
4119
  this.pendingTargets = this.pendingTargets.filter((t) => t !== el);
4325
4120
  } else {
4326
4121
  this.pendingFingerprints.push(fp);
@@ -4344,9 +4139,9 @@ var Vizu = class {
4344
4139
  return (c.status ?? "open") === this.statusFilter;
4345
4140
  }
4346
4141
  commentsForElement(fp) {
4347
- const key2 = fingerprintKey(fp);
4142
+ const key = fingerprintKey(fp);
4348
4143
  return this.comments.filter(
4349
- (c) => (c.fingerprints || []).some((f) => fingerprintKey(f) === key2)
4144
+ (c) => (c.fingerprints || []).some((f) => fingerprintKey(f) === key)
4350
4145
  );
4351
4146
  }
4352
4147
  async addCommentFromPopover(text, fps, references, mentions, attachments) {
@@ -4370,7 +4165,7 @@ var Vizu = class {
4370
4165
  mentions: mentions ?? []
4371
4166
  };
4372
4167
  this.comments.push(c);
4373
- await this.storage.addComment(this.opts.namespace, c);
4168
+ await this.storage.addComment(this.ns, c);
4374
4169
  this.bus.emit("comment:added", { comment: c });
4375
4170
  this.popover?.update(this.commentsForElement(fps[0]));
4376
4171
  this.refreshUi();
@@ -4381,7 +4176,7 @@ var Vizu = class {
4381
4176
  const comment = this.comments[idx];
4382
4177
  this.comments = this.comments.filter((c) => c.id !== id);
4383
4178
  try {
4384
- await this.storage.removeComment(this.opts.namespace, id);
4179
+ await this.storage.removeComment(this.ns, id);
4385
4180
  } catch (err) {
4386
4181
  this.comments.splice(idx, 0, comment);
4387
4182
  const status = err?.status;
@@ -4479,8 +4274,8 @@ var Vizu = class {
4479
4274
  this.spotlights.delete(entry);
4480
4275
  }, 1800);
4481
4276
  }
4482
- const key2 = fingerprintKey(fp);
4483
- const outline = this.outlines.get(key2)?.outline;
4277
+ const key = fingerprintKey(fp);
4278
+ const outline = this.outlines.get(key)?.outline;
4484
4279
  if (outline) {
4485
4280
  setTimeout(() => {
4486
4281
  outline.classList.remove("is-pulsing");
@@ -4503,8 +4298,8 @@ var Vizu = class {
4503
4298
  const bestConf = (a, b) => a === void 0 ? b : rankConf(b) > rankConf(a) ? b : a;
4504
4299
  for (const c of this.comments) {
4505
4300
  for (const fp of c.fingerprints || []) {
4506
- const key2 = fingerprintKey(fp);
4507
- let bucket = byElement.get(key2);
4301
+ const key = fingerprintKey(fp);
4302
+ let bucket = byElement.get(key);
4508
4303
  if (!bucket) {
4509
4304
  const match = findByFingerprint(fp);
4510
4305
  bucket = {
@@ -4513,7 +4308,7 @@ var Vizu = class {
4513
4308
  confidence: match.confidence,
4514
4309
  commentIds: []
4515
4310
  };
4516
- byElement.set(key2, bucket);
4311
+ byElement.set(key, bucket);
4517
4312
  }
4518
4313
  bucket.commentIds.push(c.id);
4519
4314
  perComment.set(c.id, bestConf(perComment.get(c.id), bucket.confidence));
@@ -4532,7 +4327,7 @@ var Vizu = class {
4532
4327
  if (this.resolvedConfidence.get(c.id) !== "drifted") continue;
4533
4328
  void this.selfHealComment(c, byElement);
4534
4329
  }
4535
- for (const [key2, info] of byElement) {
4330
+ for (const [key, info] of byElement) {
4536
4331
  if (info.confidence === "orphaned" || !info.target) continue;
4537
4332
  const idsForFilter = info.commentIds.filter((id) => this.commentMatchesStatusFilter(id));
4538
4333
  if (idsForFilter.length === 0) continue;
@@ -4540,11 +4335,11 @@ var Vizu = class {
4540
4335
  outlineEl.className = "vz-comment-outline";
4541
4336
  if (info.confidence === "drifted") outlineEl.classList.add("vz-comment-outline-drifted");
4542
4337
  this.root.appendChild(outlineEl);
4543
- this.outlines.set(key2, { outline: outlineEl, target: info.target, fp: info.fp });
4338
+ this.outlines.set(key, { outline: outlineEl, target: info.target, fp: info.fp });
4544
4339
  const marker = document.createElement("button");
4545
4340
  marker.className = "vz-marker";
4546
4341
  if (info.confidence === "drifted") marker.classList.add("vz-marker-drifted");
4547
- marker.setAttribute("data-element-key", key2);
4342
+ marker.setAttribute("data-element-key", key);
4548
4343
  marker.setAttribute("data-confidence", info.confidence);
4549
4344
  const total = idsForFilter.length;
4550
4345
  if (total > 1) {
@@ -4563,9 +4358,9 @@ var Vizu = class {
4563
4358
  this.openPopoverFor([info.target], [info.fp], elComments);
4564
4359
  }
4565
4360
  });
4566
- marker.addEventListener("mouseenter", () => this.pulseRelatedElements(idsForFilter, key2));
4361
+ marker.addEventListener("mouseenter", () => this.pulseRelatedElements(idsForFilter, key));
4567
4362
  this.root.appendChild(marker);
4568
- this.markers.set(key2, { marker, target: info.target, fp: info.fp, commentIds: idsForFilter });
4363
+ this.markers.set(key, { marker, target: info.target, fp: info.fp, commentIds: idsForFilter });
4569
4364
  if (info.target) {
4570
4365
  this.positionMarker(marker, info.target);
4571
4366
  this.positionOutline(outlineEl, info.target);
@@ -4588,8 +4383,8 @@ var Vizu = class {
4588
4383
  if (k !== selfKey) relatedKeys.add(k);
4589
4384
  }
4590
4385
  }
4591
- for (const key2 of relatedKeys) {
4592
- const outline = this.outlines.get(key2)?.outline;
4386
+ for (const key of relatedKeys) {
4387
+ const outline = this.outlines.get(key)?.outline;
4593
4388
  if (!outline) continue;
4594
4389
  outline.classList.remove("is-pulsing");
4595
4390
  void outline.offsetWidth;
@@ -4623,22 +4418,22 @@ var Vizu = class {
4623
4418
  syncActiveAnchorOutlines(fps) {
4624
4419
  if (!this.root) return;
4625
4420
  const wanted = new Set(fps.map((fp) => fingerprintKey(fp)));
4626
- for (const [key2, info] of this.activeAnchorOutlines) {
4627
- if (!wanted.has(key2)) {
4421
+ for (const [key, info] of this.activeAnchorOutlines) {
4422
+ if (!wanted.has(key)) {
4628
4423
  info.el.remove();
4629
- this.activeAnchorOutlines.delete(key2);
4424
+ this.activeAnchorOutlines.delete(key);
4630
4425
  }
4631
4426
  }
4632
4427
  for (const fp of fps) {
4633
- const key2 = fingerprintKey(fp);
4634
- if (this.activeAnchorOutlines.has(key2)) continue;
4428
+ const key = fingerprintKey(fp);
4429
+ if (this.activeAnchorOutlines.has(key)) continue;
4635
4430
  const outline = document.createElement("div");
4636
4431
  outline.className = "vz-comment-outline is-selected-pending";
4637
4432
  this.root.appendChild(outline);
4638
4433
  const target = findElementByFingerprint(fp);
4639
4434
  if (target) this.positionOutline(outline, target);
4640
4435
  else outline.style.display = "none";
4641
- this.activeAnchorOutlines.set(key2, { el: outline, fp });
4436
+ this.activeAnchorOutlines.set(key, { el: outline, fp });
4642
4437
  }
4643
4438
  }
4644
4439
  clearActiveAnchorOutlines() {
@@ -4686,9 +4481,6 @@ function fallbackCopy(text) {
4686
4481
  // Annotate the CommonJS export names for ESM import in node:
4687
4482
  0 && (module.exports = {
4688
4483
  CloudStorageAdapter,
4689
- InMemoryStorageAdapter,
4690
- LocalStorageAdapter,
4691
- NullStorageAdapter,
4692
4484
  SCHEMA_VERSION,
4693
4485
  Vizu,
4694
4486
  findByFingerprint,