@mahmulp/feedback-sdk 0.1.0 → 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/mock.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mock.ts"],"names":[],"mappings":"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","file":"mock.cjs","sourcesContent":["import type {\r\n CreateFeedbackInput,\r\n Feedback,\r\n FeedbackAuthor,\r\n FeedbackCoordinates,\r\n FeedbackStatus,\r\n FeedbackTransport,\r\n ListFeedbackQuery,\r\n ListFeedbackResult,\r\n} from \"./types.js\";\r\n\r\n/**\r\n * In-memory transport for local development / demos / tests.\r\n *\r\n * import { createMockTransport } from '@mahmulp/feedback-sdk/mock'\r\n *\r\n * No persistence, no network. Useful before the API is wired up.\r\n */\r\n\r\nexport interface MockTransportOptions {\r\n /** Optional initial feedback records (e.g. seeded from a fixture). */\r\n initial?: Feedback[];\r\n /** Override id generation (defaults to `fb_<timestamp>_<rand>`). */\r\n generateId?: () => string;\r\n /** Optional fake latency in ms, applied to every call. */\r\n latencyMs?: number;\r\n}\r\n\r\nexport function createMockTransport(options: MockTransportOptions = {}): FeedbackTransport & {\r\n /** Direct read of the in-memory store for tests / debugging. */\r\n _all(): Feedback[];\r\n _reset(): void;\r\n} {\r\n const items: Feedback[] = options.initial ? options.initial.map(clone) : [];\r\n const genId = options.generateId ?? defaultGenerateId;\r\n const latency = options.latencyMs ?? 0;\r\n\r\n async function delay() {\r\n if (latency > 0) await new Promise((r) => setTimeout(r, latency));\r\n }\r\n\r\n return {\r\n async list(query: ListFeedbackQuery): Promise<ListFeedbackResult> {\r\n await delay();\r\n // When `projectId` is empty, the SDK is relying on the API to scope by\r\n // key — return every record. When set, scope by it.\r\n const filtered = items\r\n .filter((f) => (query.projectId ? f.projectId === query.projectId : true))\r\n .filter((f) => (query.pageUrl ? f.pageUrl === query.pageUrl : true))\r\n .filter((f) => (query.status ? f.status === query.status : true))\r\n .map(clone);\r\n return { items: filtered };\r\n },\r\n\r\n async create(input: CreateFeedbackInput): Promise<Feedback> {\r\n await delay();\r\n const now = new Date().toISOString();\r\n const id = genId();\r\n const fb: Feedback = {\r\n id,\r\n projectId: input.projectId,\r\n pageUrl: input.pageUrl,\r\n selector: input.selector,\r\n coordinates: { ...input.coordinates },\r\n viewport: { ...input.viewport },\r\n status: \"open\",\r\n thread: input.comment\r\n ? [\r\n {\r\n id: `cm_${id}_0`,\r\n author: { ...input.comment.author },\r\n body: input.comment.body,\r\n createdAt: now,\r\n },\r\n ]\r\n : [],\r\n createdAt: now,\r\n updatedAt: now,\r\n };\r\n items.push(fb);\r\n return clone(fb);\r\n },\r\n\r\n async reply(\r\n feedbackId: string,\r\n comment: { author: FeedbackAuthor; body: string }\r\n ): Promise<Feedback> {\r\n await delay();\r\n const fb = items.find((f) => f.id === feedbackId);\r\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\r\n const now = new Date().toISOString();\r\n fb.thread.push({\r\n id: `cm_${feedbackId}_${fb.thread.length}`,\r\n author: { ...comment.author },\r\n body: comment.body,\r\n createdAt: now,\r\n });\r\n fb.updatedAt = now;\r\n return clone(fb);\r\n },\r\n\r\n async setStatus(feedbackId: string, status: FeedbackStatus): Promise<Feedback> {\r\n await delay();\r\n const fb = items.find((f) => f.id === feedbackId);\r\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\r\n fb.status = status;\r\n fb.updatedAt = new Date().toISOString();\r\n return clone(fb);\r\n },\r\n\r\n async move(feedbackId: string, coordinates: FeedbackCoordinates): Promise<Feedback> {\r\n await delay();\r\n const fb = items.find((f) => f.id === feedbackId);\r\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\r\n fb.coordinates = { ...coordinates };\r\n fb.updatedAt = new Date().toISOString();\r\n return clone(fb);\r\n },\r\n\r\n async uploadScreenshot(feedbackId: string, _blob: Blob): Promise<Feedback> {\r\n await delay();\r\n const fb = items.find((f) => f.id === feedbackId);\r\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\r\n fb.screenshotKey = `screenshots/${feedbackId}.png`;\r\n fb.updatedAt = new Date().toISOString();\r\n return clone(fb);\r\n },\r\n\r\n _all() {\r\n return items.map(clone);\r\n },\r\n _reset() {\r\n items.length = 0;\r\n },\r\n };\r\n}\r\n\r\nfunction defaultGenerateId(): string {\r\n return `fb_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\r\n}\r\n\r\nfunction clone<T>(value: T): T {\r\n // Structured clone is fine here; the records are plain JSON shapes.\r\n return JSON.parse(JSON.stringify(value)) as T;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/mock.ts"],"names":[],"mappings":"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","file":"mock.cjs","sourcesContent":["import type {\n CreateFeedbackInput,\n Feedback,\n FeedbackAuthor,\n FeedbackCoordinates,\n FeedbackStatus,\n FeedbackTransport,\n ListFeedbackQuery,\n ListFeedbackResult,\n} from \"./types.js\";\n\n/**\n * In-memory transport for local development / demos / tests.\n *\n * import { createMockTransport } from '@mahmulp/feedback-sdk/mock'\n *\n * No persistence, no network. Useful before the API is wired up.\n */\n\nexport interface MockTransportOptions {\n /** Optional initial feedback records (e.g. seeded from a fixture). */\n initial?: Feedback[];\n /** Override id generation (defaults to `fb_<timestamp>_<rand>`). */\n generateId?: () => string;\n /** Optional fake latency in ms, applied to every call. */\n latencyMs?: number;\n}\n\nexport function createMockTransport(options: MockTransportOptions = {}): FeedbackTransport & {\n /** Direct read of the in-memory store for tests / debugging. */\n _all(): Feedback[];\n _reset(): void;\n} {\n const items: Feedback[] = options.initial ? options.initial.map(clone) : [];\n const genId = options.generateId ?? defaultGenerateId;\n const latency = options.latencyMs ?? 0;\n\n async function delay() {\n if (latency > 0) await new Promise((r) => setTimeout(r, latency));\n }\n\n return {\n async list(query: ListFeedbackQuery): Promise<ListFeedbackResult> {\n await delay();\n // When `projectId` is empty, the SDK is relying on the API to scope by\n // key — return every record. When set, scope by it.\n const filtered = items\n .filter((f) => (query.projectId ? f.projectId === query.projectId : true))\n .filter((f) => (query.pageUrl ? f.pageUrl === query.pageUrl : true))\n .filter((f) => (query.status ? f.status === query.status : true))\n .map(clone);\n return { items: filtered };\n },\n\n async create(input: CreateFeedbackInput): Promise<Feedback> {\n await delay();\n const now = new Date().toISOString();\n const id = genId();\n const fb: Feedback = {\n id,\n projectId: input.projectId,\n pageUrl: input.pageUrl,\n selector: input.selector,\n coordinates: { ...input.coordinates },\n viewport: { ...input.viewport },\n status: \"open\",\n thread: input.comment\n ? [\n {\n id: `cm_${id}_0`,\n author: { ...input.comment.author },\n body: input.comment.body,\n createdAt: now,\n },\n ]\n : [],\n createdAt: now,\n updatedAt: now,\n };\n items.push(fb);\n return clone(fb);\n },\n\n async reply(\n feedbackId: string,\n comment: { author: FeedbackAuthor; body: string }\n ): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n const now = new Date().toISOString();\n fb.thread.push({\n id: `cm_${feedbackId}_${fb.thread.length}`,\n author: { ...comment.author },\n body: comment.body,\n createdAt: now,\n });\n fb.updatedAt = now;\n return clone(fb);\n },\n\n async setStatus(feedbackId: string, status: FeedbackStatus): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n fb.status = status;\n fb.updatedAt = new Date().toISOString();\n return clone(fb);\n },\n\n async move(feedbackId: string, coordinates: FeedbackCoordinates): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n fb.coordinates = { ...coordinates };\n fb.updatedAt = new Date().toISOString();\n return clone(fb);\n },\n\n async uploadScreenshot(feedbackId: string, _blob: Blob): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n fb.screenshotKey = `screenshots/${feedbackId}.png`;\n fb.updatedAt = new Date().toISOString();\n return clone(fb);\n },\n\n _all() {\n return items.map(clone);\n },\n _reset() {\n items.length = 0;\n },\n };\n}\n\nfunction defaultGenerateId(): string {\n return `fb_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction clone<T>(value: T): T {\n // Structured clone is fine here; the records are plain JSON shapes.\n return JSON.parse(JSON.stringify(value)) as T;\n}\n"]}
package/dist/mock.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mock.ts"],"names":[],"mappings":"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","file":"mock.js","sourcesContent":["import type {\r\n CreateFeedbackInput,\r\n Feedback,\r\n FeedbackAuthor,\r\n FeedbackCoordinates,\r\n FeedbackStatus,\r\n FeedbackTransport,\r\n ListFeedbackQuery,\r\n ListFeedbackResult,\r\n} from \"./types.js\";\r\n\r\n/**\r\n * In-memory transport for local development / demos / tests.\r\n *\r\n * import { createMockTransport } from '@mahmulp/feedback-sdk/mock'\r\n *\r\n * No persistence, no network. Useful before the API is wired up.\r\n */\r\n\r\nexport interface MockTransportOptions {\r\n /** Optional initial feedback records (e.g. seeded from a fixture). */\r\n initial?: Feedback[];\r\n /** Override id generation (defaults to `fb_<timestamp>_<rand>`). */\r\n generateId?: () => string;\r\n /** Optional fake latency in ms, applied to every call. */\r\n latencyMs?: number;\r\n}\r\n\r\nexport function createMockTransport(options: MockTransportOptions = {}): FeedbackTransport & {\r\n /** Direct read of the in-memory store for tests / debugging. */\r\n _all(): Feedback[];\r\n _reset(): void;\r\n} {\r\n const items: Feedback[] = options.initial ? options.initial.map(clone) : [];\r\n const genId = options.generateId ?? defaultGenerateId;\r\n const latency = options.latencyMs ?? 0;\r\n\r\n async function delay() {\r\n if (latency > 0) await new Promise((r) => setTimeout(r, latency));\r\n }\r\n\r\n return {\r\n async list(query: ListFeedbackQuery): Promise<ListFeedbackResult> {\r\n await delay();\r\n // When `projectId` is empty, the SDK is relying on the API to scope by\r\n // key — return every record. When set, scope by it.\r\n const filtered = items\r\n .filter((f) => (query.projectId ? f.projectId === query.projectId : true))\r\n .filter((f) => (query.pageUrl ? f.pageUrl === query.pageUrl : true))\r\n .filter((f) => (query.status ? f.status === query.status : true))\r\n .map(clone);\r\n return { items: filtered };\r\n },\r\n\r\n async create(input: CreateFeedbackInput): Promise<Feedback> {\r\n await delay();\r\n const now = new Date().toISOString();\r\n const id = genId();\r\n const fb: Feedback = {\r\n id,\r\n projectId: input.projectId,\r\n pageUrl: input.pageUrl,\r\n selector: input.selector,\r\n coordinates: { ...input.coordinates },\r\n viewport: { ...input.viewport },\r\n status: \"open\",\r\n thread: input.comment\r\n ? [\r\n {\r\n id: `cm_${id}_0`,\r\n author: { ...input.comment.author },\r\n body: input.comment.body,\r\n createdAt: now,\r\n },\r\n ]\r\n : [],\r\n createdAt: now,\r\n updatedAt: now,\r\n };\r\n items.push(fb);\r\n return clone(fb);\r\n },\r\n\r\n async reply(\r\n feedbackId: string,\r\n comment: { author: FeedbackAuthor; body: string }\r\n ): Promise<Feedback> {\r\n await delay();\r\n const fb = items.find((f) => f.id === feedbackId);\r\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\r\n const now = new Date().toISOString();\r\n fb.thread.push({\r\n id: `cm_${feedbackId}_${fb.thread.length}`,\r\n author: { ...comment.author },\r\n body: comment.body,\r\n createdAt: now,\r\n });\r\n fb.updatedAt = now;\r\n return clone(fb);\r\n },\r\n\r\n async setStatus(feedbackId: string, status: FeedbackStatus): Promise<Feedback> {\r\n await delay();\r\n const fb = items.find((f) => f.id === feedbackId);\r\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\r\n fb.status = status;\r\n fb.updatedAt = new Date().toISOString();\r\n return clone(fb);\r\n },\r\n\r\n async move(feedbackId: string, coordinates: FeedbackCoordinates): Promise<Feedback> {\r\n await delay();\r\n const fb = items.find((f) => f.id === feedbackId);\r\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\r\n fb.coordinates = { ...coordinates };\r\n fb.updatedAt = new Date().toISOString();\r\n return clone(fb);\r\n },\r\n\r\n async uploadScreenshot(feedbackId: string, _blob: Blob): Promise<Feedback> {\r\n await delay();\r\n const fb = items.find((f) => f.id === feedbackId);\r\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\r\n fb.screenshotKey = `screenshots/${feedbackId}.png`;\r\n fb.updatedAt = new Date().toISOString();\r\n return clone(fb);\r\n },\r\n\r\n _all() {\r\n return items.map(clone);\r\n },\r\n _reset() {\r\n items.length = 0;\r\n },\r\n };\r\n}\r\n\r\nfunction defaultGenerateId(): string {\r\n return `fb_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\r\n}\r\n\r\nfunction clone<T>(value: T): T {\r\n // Structured clone is fine here; the records are plain JSON shapes.\r\n return JSON.parse(JSON.stringify(value)) as T;\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/mock.ts"],"names":[],"mappings":"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","file":"mock.js","sourcesContent":["import type {\n CreateFeedbackInput,\n Feedback,\n FeedbackAuthor,\n FeedbackCoordinates,\n FeedbackStatus,\n FeedbackTransport,\n ListFeedbackQuery,\n ListFeedbackResult,\n} from \"./types.js\";\n\n/**\n * In-memory transport for local development / demos / tests.\n *\n * import { createMockTransport } from '@mahmulp/feedback-sdk/mock'\n *\n * No persistence, no network. Useful before the API is wired up.\n */\n\nexport interface MockTransportOptions {\n /** Optional initial feedback records (e.g. seeded from a fixture). */\n initial?: Feedback[];\n /** Override id generation (defaults to `fb_<timestamp>_<rand>`). */\n generateId?: () => string;\n /** Optional fake latency in ms, applied to every call. */\n latencyMs?: number;\n}\n\nexport function createMockTransport(options: MockTransportOptions = {}): FeedbackTransport & {\n /** Direct read of the in-memory store for tests / debugging. */\n _all(): Feedback[];\n _reset(): void;\n} {\n const items: Feedback[] = options.initial ? options.initial.map(clone) : [];\n const genId = options.generateId ?? defaultGenerateId;\n const latency = options.latencyMs ?? 0;\n\n async function delay() {\n if (latency > 0) await new Promise((r) => setTimeout(r, latency));\n }\n\n return {\n async list(query: ListFeedbackQuery): Promise<ListFeedbackResult> {\n await delay();\n // When `projectId` is empty, the SDK is relying on the API to scope by\n // key — return every record. When set, scope by it.\n const filtered = items\n .filter((f) => (query.projectId ? f.projectId === query.projectId : true))\n .filter((f) => (query.pageUrl ? f.pageUrl === query.pageUrl : true))\n .filter((f) => (query.status ? f.status === query.status : true))\n .map(clone);\n return { items: filtered };\n },\n\n async create(input: CreateFeedbackInput): Promise<Feedback> {\n await delay();\n const now = new Date().toISOString();\n const id = genId();\n const fb: Feedback = {\n id,\n projectId: input.projectId,\n pageUrl: input.pageUrl,\n selector: input.selector,\n coordinates: { ...input.coordinates },\n viewport: { ...input.viewport },\n status: \"open\",\n thread: input.comment\n ? [\n {\n id: `cm_${id}_0`,\n author: { ...input.comment.author },\n body: input.comment.body,\n createdAt: now,\n },\n ]\n : [],\n createdAt: now,\n updatedAt: now,\n };\n items.push(fb);\n return clone(fb);\n },\n\n async reply(\n feedbackId: string,\n comment: { author: FeedbackAuthor; body: string }\n ): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n const now = new Date().toISOString();\n fb.thread.push({\n id: `cm_${feedbackId}_${fb.thread.length}`,\n author: { ...comment.author },\n body: comment.body,\n createdAt: now,\n });\n fb.updatedAt = now;\n return clone(fb);\n },\n\n async setStatus(feedbackId: string, status: FeedbackStatus): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n fb.status = status;\n fb.updatedAt = new Date().toISOString();\n return clone(fb);\n },\n\n async move(feedbackId: string, coordinates: FeedbackCoordinates): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n fb.coordinates = { ...coordinates };\n fb.updatedAt = new Date().toISOString();\n return clone(fb);\n },\n\n async uploadScreenshot(feedbackId: string, _blob: Blob): Promise<Feedback> {\n await delay();\n const fb = items.find((f) => f.id === feedbackId);\n if (!fb) throw new Error(`feedback not found: ${feedbackId}`);\n fb.screenshotKey = `screenshots/${feedbackId}.png`;\n fb.updatedAt = new Date().toISOString();\n return clone(fb);\n },\n\n _all() {\n return items.map(clone);\n },\n _reset() {\n items.length = 0;\n },\n };\n}\n\nfunction defaultGenerateId(): string {\n return `fb_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction clone<T>(value: T): T {\n // Structured clone is fine here; the records are plain JSON shapes.\n return JSON.parse(JSON.stringify(value)) as T;\n}\n"]}
package/dist/svelte.js CHANGED
@@ -182,7 +182,10 @@ var LauncherManager = class {
182
182
  __publicField(this, "state", {
183
183
  enabled: false,
184
184
  pinsVisible: true,
185
- launcherVisible: true
185
+ // Start collapsed — only the small reveal bubble is visible until the
186
+ // user clicks it. Less visual weight on the prototype out of the box;
187
+ // user opts in by clicking the bubble icon in the bottom-right.
188
+ launcherVisible: false
186
189
  });
187
190
  this.launcherEl = document.createElement("div");
188
191
  this.launcherEl.className = "launcher";
@@ -234,6 +237,7 @@ var LauncherManager = class {
234
237
  /** True when an event originated from the launcher's own UI. */
235
238
  ownsNode(node) {
236
239
  if (!node) return false;
240
+ if (typeof Node === "undefined" || !(node instanceof Node)) return false;
237
241
  return this.launcherEl.contains(node) || this.revealEl.contains(node);
238
242
  }
239
243
  destroy() {
@@ -315,11 +319,34 @@ var PopoverManager = class {
315
319
  });
316
320
  return el;
317
321
  }
322
+ /** True when a thread for this feedback is already open. */
323
+ isThreadOpenFor(feedbackId) {
324
+ return this.current?.type === "thread" && this.current.feedbackId === feedbackId;
325
+ }
318
326
  showThread(feedback2, anchor, cb) {
327
+ if (this.current?.type === "thread" && this.current.feedbackId === feedback2.id) {
328
+ const fresh = this.buildThread(feedback2, cb);
329
+ this.current.el.replaceWith(fresh);
330
+ this.current = {
331
+ type: "thread",
332
+ el: fresh,
333
+ pageX: anchor.pageX,
334
+ pageY: anchor.pageY,
335
+ feedbackId: feedback2.id
336
+ };
337
+ this.repositionInternal();
338
+ return fresh;
339
+ }
319
340
  this.hide();
320
341
  const el = this.buildThread(feedback2, cb);
321
342
  this.layer.appendChild(el);
322
- this.current = { type: "thread", el, pageX: anchor.pageX, pageY: anchor.pageY };
343
+ this.current = {
344
+ type: "thread",
345
+ el,
346
+ pageX: anchor.pageX,
347
+ pageY: anchor.pageY,
348
+ feedbackId: feedback2.id
349
+ };
323
350
  this.repositionInternal();
324
351
  return el;
325
352
  }
@@ -1027,33 +1054,65 @@ var Overlay = class {
1027
1054
  });
1028
1055
  }
1029
1056
  layoutPins() {
1030
- const next = document.createDocumentFragment();
1057
+ const existing = /* @__PURE__ */ new Map();
1058
+ for (const node of Array.from(this.pinLayer.children)) {
1059
+ const id = node.dataset.feedbackId;
1060
+ if (id) existing.set(id, node);
1061
+ }
1062
+ const desiredOrder = [];
1063
+ const seen = /* @__PURE__ */ new Set();
1031
1064
  for (const fb of this.pendingFeedback) {
1065
+ seen.add(fb.id);
1032
1066
  const target = findElement(fb.selector);
1033
1067
  const projected = projectCoordinates(target, fb.coordinates);
1034
- const pin = document.createElement("button");
1035
- pin.type = "button";
1036
- const classes = ["pin"];
1037
- if (projected.orphaned) classes.push("orphaned");
1038
- if (fb.status === "resolved") classes.push("resolved");
1039
- if (fb.status === "archived") classes.push("archived");
1040
- pin.className = classes.join(" ");
1041
- pin.setAttribute("data-feedback-id", fb.id);
1042
- pin.setAttribute("aria-label", `Feedback ${fb.id}`);
1043
- pin.style.left = `${projected.x - window.scrollX}px`;
1044
- pin.style.top = `${projected.y - window.scrollY}px`;
1045
- const label = document.createElement("span");
1046
- label.textContent = String(fb.thread.length || 1);
1047
- pin.appendChild(label);
1048
- pin.addEventListener("click", (e) => {
1049
- e.stopPropagation();
1050
- if (this.draggingPin === fb.id) return;
1051
- this.onPinClick?.(fb);
1052
- });
1053
- this.attachDragHandlers(pin, fb);
1054
- next.appendChild(pin);
1068
+ const left = `${projected.x - window.scrollX}px`;
1069
+ const top = `${projected.y - window.scrollY}px`;
1070
+ let pin = existing.get(fb.id);
1071
+ if (pin) {
1072
+ const classes = ["pin"];
1073
+ if (projected.orphaned) classes.push("orphaned");
1074
+ if (fb.status === "resolved") classes.push("resolved");
1075
+ if (fb.status === "archived") classes.push("archived");
1076
+ if (pin.classList.contains("dragging")) classes.push("dragging");
1077
+ pin.className = classes.join(" ");
1078
+ if (pin.style.left !== left) pin.style.left = left;
1079
+ if (pin.style.top !== top) pin.style.top = top;
1080
+ const label = pin.firstElementChild;
1081
+ const nextLabel = String(fb.thread.length || 1);
1082
+ if (label && label.textContent !== nextLabel) label.textContent = nextLabel;
1083
+ } else {
1084
+ pin = document.createElement("button");
1085
+ pin.type = "button";
1086
+ const classes = ["pin"];
1087
+ if (projected.orphaned) classes.push("orphaned");
1088
+ if (fb.status === "resolved") classes.push("resolved");
1089
+ if (fb.status === "archived") classes.push("archived");
1090
+ pin.className = classes.join(" ");
1091
+ pin.dataset.feedbackId = fb.id;
1092
+ pin.setAttribute("aria-label", `Feedback ${fb.id}`);
1093
+ pin.style.left = left;
1094
+ pin.style.top = top;
1095
+ const label = document.createElement("span");
1096
+ label.textContent = String(fb.thread.length || 1);
1097
+ pin.appendChild(label);
1098
+ pin.addEventListener("click", (e) => {
1099
+ e.stopPropagation();
1100
+ if (this.draggingPin === fb.id) return;
1101
+ const fresh = this.pendingFeedback.find((f) => f.id === fb.id) ?? fb;
1102
+ this.onPinClick?.(fresh);
1103
+ });
1104
+ this.attachDragHandlers(pin, fb);
1105
+ }
1106
+ desiredOrder.push(pin);
1107
+ }
1108
+ for (const [id, node] of existing) {
1109
+ if (!seen.has(id)) node.remove();
1110
+ }
1111
+ for (let i = 0; i < desiredOrder.length; i++) {
1112
+ const want = desiredOrder[i];
1113
+ const have = this.pinLayer.children[i];
1114
+ if (have !== want) this.pinLayer.insertBefore(want, have ?? null);
1055
1115
  }
1056
- this.pinLayer.replaceChildren(next);
1057
1116
  }
1058
1117
  destroy() {
1059
1118
  if (this.rafHandle !== null) {
@@ -1172,8 +1231,6 @@ async function captureViewport(options = {}) {
1172
1231
  const html2canvas = await loadHtml2Canvas();
1173
1232
  if (!html2canvas) return null;
1174
1233
  const host = document.querySelector(`[${HOST_ATTR2}]`);
1175
- const previousVisibility = host?.style.visibility ?? "";
1176
- if (host) host.style.visibility = "hidden";
1177
1234
  try {
1178
1235
  const canvas = await html2canvas(document.documentElement, {
1179
1236
  backgroundColor: null,
@@ -1186,23 +1243,37 @@ async function captureViewport(options = {}) {
1186
1243
  windowHeight: window.innerHeight,
1187
1244
  logging: false,
1188
1245
  useCORS: true,
1189
- allowTaint: false
1246
+ allowTaint: false,
1247
+ ignoreElements: (el) => {
1248
+ if (host && (el === host || host.contains(el))) return true;
1249
+ if (el instanceof HTMLElement && el.hasAttribute(HOST_ATTR2)) return true;
1250
+ return false;
1251
+ }
1190
1252
  });
1191
1253
  return await canvasToBlob(canvas, opts.mimeType, opts.quality);
1192
- } catch {
1254
+ } catch (err) {
1255
+ if (typeof console !== "undefined") {
1256
+ console.warn("[feedback-sdk] screenshot capture failed:", err);
1257
+ }
1193
1258
  return null;
1194
- } finally {
1195
- if (host) host.style.visibility = previousVisibility;
1196
1259
  }
1197
1260
  }
1198
1261
  var html2canvasPromise = null;
1262
+ var html2canvasMissingWarned = false;
1199
1263
  async function loadHtml2Canvas() {
1200
1264
  if (html2canvasPromise) return html2canvasPromise;
1201
1265
  html2canvasPromise = (async () => {
1202
1266
  try {
1203
- const mod = await import('html2canvas');
1267
+ const mod = await import('html2canvas-pro');
1204
1268
  return mod.default ?? mod ?? null;
1205
- } catch {
1269
+ } catch (err) {
1270
+ if (!html2canvasMissingWarned && typeof console !== "undefined") {
1271
+ html2canvasMissingWarned = true;
1272
+ console.warn(
1273
+ "[feedback-sdk] screenshot capture disabled: failed to load `html2canvas-pro`. This shouldn't normally happen \u2014 html2canvas-pro is a direct dependency of the SDK. Pass `captureScreenshots: false` to silence this warning if intended.",
1274
+ err
1275
+ );
1276
+ }
1206
1277
  return null;
1207
1278
  }
1208
1279
  })();
@@ -1495,8 +1566,6 @@ function initFeedback(options) {
1495
1566
  const updated = await transport.reply(fb.id, { author, body });
1496
1567
  setAuthor(author);
1497
1568
  replaceFeedback(updated);
1498
- overlay.popoverManager().hide();
1499
- activeThreadId = null;
1500
1569
  openThread(updated);
1501
1570
  } catch (err) {
1502
1571
  reportError(err);
@@ -1506,8 +1575,6 @@ function initFeedback(options) {
1506
1575
  try {
1507
1576
  const updated = await transport.setStatus(fb.id, status);
1508
1577
  replaceFeedback(updated);
1509
- overlay.popoverManager().hide();
1510
- activeThreadId = null;
1511
1578
  openThread(updated);
1512
1579
  } catch (err) {
1513
1580
  reportError(err);
@@ -1555,7 +1622,8 @@ function initFeedback(options) {
1555
1622
  }
1556
1623
  async function refresh() {
1557
1624
  try {
1558
- const result = await transport.list({ projectId: "" });
1625
+ const pageUrl = getPageUrl();
1626
+ const result = await transport.list({ projectId: "", pageUrl });
1559
1627
  state.feedbacks = result.items;
1560
1628
  overlay.renderPins(state.feedbacks, openThread, onPinDragEnd);
1561
1629
  } catch (err) {
@@ -1585,12 +1653,35 @@ function initFeedback(options) {
1585
1653
  document.addEventListener("keydown", onKeyDown, true);
1586
1654
  window.addEventListener("scroll", onWindowReposition, true);
1587
1655
  window.addEventListener("resize", onWindowReposition);
1656
+ let lastSeenUrl = getPageUrl();
1657
+ function onMaybeNavigate() {
1658
+ const next = getPageUrl();
1659
+ if (next === lastSeenUrl) return;
1660
+ lastSeenUrl = next;
1661
+ void refresh();
1662
+ }
1663
+ const originalPushState = history.pushState.bind(history);
1664
+ const originalReplaceState = history.replaceState.bind(history);
1665
+ history.pushState = function patchedPushState(...args) {
1666
+ const result = originalPushState(...args);
1667
+ queueMicrotask(onMaybeNavigate);
1668
+ return result;
1669
+ };
1670
+ history.replaceState = function patchedReplaceState(...args) {
1671
+ const result = originalReplaceState(...args);
1672
+ queueMicrotask(onMaybeNavigate);
1673
+ return result;
1674
+ };
1675
+ window.addEventListener("popstate", onMaybeNavigate);
1588
1676
  void refresh();
1589
1677
  overlay.setEnabledStyles(state.enabled);
1590
1678
  const wantsLauncher = options.showLauncher !== false;
1591
1679
  const launcherState = {
1592
1680
  pinsVisible: options.pinsVisible !== false,
1593
- launcherVisible: true
1681
+ // Default to a collapsed launcher. The user reveals it by clicking the
1682
+ // small bubble button in the bottom-right corner. This keeps the SDK's
1683
+ // visual footprint near zero on first paint of the prototype.
1684
+ launcherVisible: false
1594
1685
  };
1595
1686
  overlay.setPinsVisible(launcherState.pinsVisible);
1596
1687
  function syncLauncher() {
@@ -1647,6 +1738,9 @@ function initFeedback(options) {
1647
1738
  document.removeEventListener("keydown", onKeyDown, true);
1648
1739
  window.removeEventListener("scroll", onWindowReposition, true);
1649
1740
  window.removeEventListener("resize", onWindowReposition);
1741
+ window.removeEventListener("popstate", onMaybeNavigate);
1742
+ history.pushState = originalPushState;
1743
+ history.replaceState = originalReplaceState;
1650
1744
  overlay.destroy();
1651
1745
  }
1652
1746
  };