@sylergydigital/issue-pin-sdk 0.6.3 → 0.6.5

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/CHANGELOG.md ADDED
@@ -0,0 +1,245 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@sylergydigital/issue-pin-sdk` are documented here.
4
+
5
+ ## [0.6.5] - 2026-04-15
6
+
7
+ ### Added
8
+ - **Draggable launcher button** — the feedback launcher can now be freely dragged across the viewport using mouse, touch, or pen input (iOS AssistiveTouch pattern). On release, the button smoothly snaps to the nearest left or right viewport edge.
9
+ - Position persistence — launcher position (edge + Y offset) is saved to `localStorage` and restored across page reloads.
10
+ - Menu and hint toast auto-flip to the opposite side of the docked edge so they never render off-screen.
11
+ - Viewport resize clamping — button repositions to stay within bounds when the window is resized.
12
+ - Click vs. drag disambiguation — short interactions (< 5px movement) still toggle the menu normally.
13
+
14
+ ## [0.6.4] - 2026-04-14
15
+
16
+ ### Fixed
17
+ - Included `CHANGELOG.md` in the SDK npm publish pipeline (`files` array in package.json).
18
+
19
+ ### Added
20
+ - SDK CHANGELOG.md with full release history.
21
+
22
+ ## [0.6.3] - 2026-04-14
23
+
24
+ ### Added
25
+ - **UUID normalization for non-UUID auth providers** — SDK auto-converts Firebase, Auth0, Cognito, and other non-UUID user IDs to deterministic UUID v5 format at the SDK boundary. Existing UUID-format IDs pass through unchanged.
26
+ - `normalizeUserId()` / `toSupabaseUuid()` — public utility export for converting user IDs outside the SDK component lifecycle.
27
+ - `CSP_REQUIREMENTS` — typed constant exporting all Content Security Policy directives required by the SDK (`connect-src`, `img-src`, `style-src`), usable in server middleware or build-time CSP generation.
28
+ - Runtime CSP violation detection — `subscribeCspDetection()` automatically activates on SDK mount and logs actionable `[IssuePin]` console warnings when CSP blocks SDK functionality. Per-directive deduplication prevents warning spam.
29
+ - CSP documentation section in SDK README with copy-paste directive block, per-directive explanation table, and programmatic access example.
30
+ - Debug logging for ID conversions — when `debug={true}`, logs `[EW SDK] User ID normalized: "{raw}" -> "{effective}"` for converted IDs.
31
+
32
+ ### Fixed
33
+ - Stale closure in identity warning gated on `authReady` state.
34
+ - Missing SSR guard in `subscribeCspDetection` (safe for server-side rendering).
35
+
36
+ ### Dependencies
37
+ - Added `uuid@^11.0.3` as bundled dependency (CommonJS-compatible).
38
+
39
+ ## [0.6.2] - 2026-04-02
40
+
41
+ ### Fixed
42
+ - Replaced `html2canvas` with `html2canvas-pro` to support modern CSS features like `oklab` color space.
43
+
44
+ ## [0.6.1] - 2026-03-24
45
+
46
+ ### Changed
47
+ - Set up public SDK mirror and npm release pipeline.
48
+
49
+ ## [0.6.0] - 2026-03-24
50
+
51
+ ### Changed
52
+ - Set up public SDK mirror and npm release pipeline.
53
+
54
+ ## [0.5.9] - 2026-03-24
55
+
56
+ ### Changed
57
+ - Public SDK mirror and npm registry setup.
58
+
59
+ ## [0.5.8] - 2026-03-23
60
+
61
+ ### Added
62
+ - `showHints` toggle for instructional UI hints in the SDK.
63
+
64
+ ## [0.5.7] - 2026-03-23
65
+
66
+ ### Added
67
+ - Verified LMS federation rollout (patch).
68
+
69
+ ## [0.5.6] - 2026-03-23
70
+
71
+ ### Added
72
+ - LMS federation rollout with verification.
73
+
74
+ ## [0.5.5] - 2026-03-22
75
+
76
+ ### Fixed
77
+ - SDK build and packaging fixes.
78
+
79
+ ## [0.5.4] - 2026-03-22
80
+
81
+ ### Fixed
82
+ - SDK build and packaging fixes.
83
+
84
+ ## [0.5.3] - 2026-03-22
85
+
86
+ ### Changed
87
+ - Aligned SDK docs and package metadata with router-free integration.
88
+
89
+ ## [0.5.2] - 2026-03-22
90
+
91
+ ### Added
92
+ - Iframe review mode for SDK pins (canvas review surface).
93
+
94
+ ## [0.5.1] - 2026-03-22
95
+
96
+ ### Added
97
+ - Anonymous screenshot reads for SDK workspaces via Supabase RLS.
98
+ - ThreadPins test coverage.
99
+ - npm registry publishing.
100
+
101
+ ## [0.5.0] - 2026-03-22
102
+
103
+ ### Changed
104
+ - npm versioning aligned with git tags.
105
+
106
+ ## [0.4.9] - 2026-03-22
107
+
108
+ ### Changed
109
+ - Vite host allowlist and gitignore for local tooling.
110
+
111
+ ## [0.4.8] - 2026-03-17
112
+
113
+ ### Added
114
+ - SDK customization APIs for launcher visibility and pin visibility control.
115
+
116
+ ## [0.4.7] - 2026-03-17
117
+
118
+ ### Fixed
119
+ - SDK publish build for controlled pin mode.
120
+
121
+ ## [0.4.6] - 2026-03-17
122
+
123
+ ### Changed
124
+ - Thread pins open in a new tab.
125
+
126
+ ## [0.4.5] - 2026-03-17
127
+
128
+ ### Changed
129
+ - Rewrote SDK pin positioning lifecycle for stability.
130
+
131
+ ## [0.4.4] - 2026-03-17
132
+
133
+ ### Fixed
134
+ - Stabilized SDK pin rehydration observer.
135
+
136
+ ## [0.4.3] - 2026-03-17
137
+
138
+ ### Changed
139
+ - Allow anonymous `sdk-resolve` calls.
140
+
141
+ ## [0.4.2] - 2026-03-17
142
+
143
+ ### Changed
144
+ - Allow commenter workspace memberships.
145
+
146
+ ## [0.4.1] - 2026-03-17
147
+
148
+ ### Added
149
+ - Documentation for federated first-write gating.
150
+
151
+ ## [0.4.0] - 2026-03-17
152
+
153
+ ### Fixed
154
+ - Federated SDK author attribution.
155
+
156
+ ## [0.3.4] - 2026-03-16
157
+
158
+ ### Added
159
+ - Anonymous update RLS policy.
160
+
161
+ ## [0.3.3] - 2026-03-16
162
+
163
+ ### Changed
164
+ - Enabled React Router v7 future flag.
165
+
166
+ ## [0.3.2] - 2026-03-16
167
+
168
+ ### Changed
169
+ - Relaxed thread comments foreign key constraint.
170
+
171
+ ## [0.3.1] - 2026-03-16
172
+
173
+ ### Added
174
+ - RLS policies for anonymous inserts.
175
+
176
+ ## [0.3.0] - 2026-03-16
177
+
178
+ ### Fixed
179
+ - SDK feedback launcher click behavior.
180
+
181
+ ## [0.2.9] - 2026-03-16
182
+
183
+ ### Added
184
+ - Hardcoded SDK theme for consistent styling.
185
+
186
+ ## [0.2.8] - 2026-03-16
187
+
188
+ ### Fixed
189
+ - FeedbackButton `.d.ts` build ordering.
190
+
191
+ ## [0.2.7] - 2026-03-16
192
+
193
+ ### Added
194
+ - Debug logging for SDK menu state.
195
+
196
+ ### Fixed
197
+ - SDK menu state management hardened.
198
+
199
+ ## [0.2.6] - 2026-03-16
200
+
201
+ ### Fixed
202
+ - SDK feedback launcher click handling.
203
+
204
+ ## [0.2.5] - 2026-03-16
205
+
206
+ ### Fixed
207
+ - FeedbackButton interaction reliability.
208
+
209
+ ## [0.2.4] - 2026-03-16
210
+
211
+ ### Fixed
212
+ - Stabilized `getSignedUrlRef` for screenshot storage.
213
+
214
+ ## [0.2.3] - 2026-03-16
215
+
216
+ ### Added
217
+ - Error boundary wrapping for ThreadPins.
218
+
219
+ ## [0.2.0] - 2026-03-16
220
+
221
+ ### Changed
222
+ - Renamed SDK package to `@sylergydigital/issue-pin-sdk`.
223
+
224
+ ## [0.1.8] - 2026-03-16
225
+
226
+ ### Fixed
227
+ - TS2722 error in ScreenshotFeedback component.
228
+
229
+ ## [0.1.6] - 2026-03-16
230
+
231
+ ### Fixed
232
+ - Viewport auto-screenshot capture.
233
+
234
+ ## [0.1.5] - 2026-03-16
235
+
236
+ ### Fixed
237
+ - Auto-screenshot fetch reliability.
238
+
239
+ ## [0.1.0] - 2026-03-16
240
+
241
+ ### Added
242
+ - Initial SDK release as `@sylergydigital/issue-pin-sdk`.
243
+ - `<IssuePin />` drop-in component with pin annotations, screenshot capture, and threaded discussions.
244
+ - Supabase-backed real-time data layer.
245
+ - Federated user identity for SDK consumers.
package/dist/index.cjs CHANGED
@@ -2168,10 +2168,52 @@ function getLauncherCapabilities({
2168
2168
 
2169
2169
  // src/FeedbackButton.tsx
2170
2170
  var import_jsx_runtime7 = require("react/jsx-runtime");
2171
+ var LAUNCHER_POS_KEY = "issue-pin:launcher-pos";
2172
+ var DRAG_THRESHOLD = 5;
2173
+ var EDGE_MARGIN = 20;
2174
+ var BTN_SIZE = 48;
2175
+ var SNAP_EASING = "cubic-bezier(0.2, 0, 0, 1)";
2176
+ var SNAP_TRANSITION = `left 0.3s ${SNAP_EASING}, top 0.3s ${SNAP_EASING}`;
2177
+ function loadPos(initialEdge) {
2178
+ try {
2179
+ const raw = localStorage.getItem(LAUNCHER_POS_KEY);
2180
+ if (raw) {
2181
+ const parsed = JSON.parse(raw);
2182
+ if ((parsed.edge === "left" || parsed.edge === "right") && typeof parsed.y === "number") {
2183
+ return { edge: parsed.edge, y: parsed.y };
2184
+ }
2185
+ }
2186
+ } catch {
2187
+ }
2188
+ return {
2189
+ edge: initialEdge === "bottom-left" ? "left" : "right",
2190
+ y: window.innerHeight - BTN_SIZE - EDGE_MARGIN
2191
+ };
2192
+ }
2193
+ function savePos(pos) {
2194
+ try {
2195
+ localStorage.setItem(LAUNCHER_POS_KEY, JSON.stringify(pos));
2196
+ } catch {
2197
+ }
2198
+ }
2199
+ function clampY(y) {
2200
+ return Math.min(
2201
+ Math.max(y, EDGE_MARGIN),
2202
+ window.innerHeight - BTN_SIZE - EDGE_MARGIN
2203
+ );
2204
+ }
2205
+ function snapXForEdge(edge) {
2206
+ return edge === "left" ? EDGE_MARGIN : window.innerWidth - BTN_SIZE - EDGE_MARGIN;
2207
+ }
2171
2208
  function FeedbackButton({ position = "bottom-right" }) {
2172
2209
  const ctx = useFeedbackSafe();
2173
2210
  const menuRef = (0, import_react9.useRef)(null);
2174
2211
  const [hintDismissed, setHintDismissed] = (0, import_react9.useState)(false);
2212
+ const [pos, setPos] = (0, import_react9.useState)(() => loadPos(position));
2213
+ const [dragXY, setDragXY] = (0, import_react9.useState)(null);
2214
+ const [snapping, setSnapping] = (0, import_react9.useState)(false);
2215
+ const dragStateRef = (0, import_react9.useRef)(null);
2216
+ const snapTimerRef = (0, import_react9.useRef)(null);
2175
2217
  const menuOpenState = ctx?.menuOpen ?? false;
2176
2218
  const debug = ctx?.debug ?? false;
2177
2219
  (0, import_react9.useEffect)(() => {
@@ -2189,6 +2231,79 @@ function FeedbackButton({ position = "bottom-right" }) {
2189
2231
  console.log("[EW SDK] FeedbackButton mounted");
2190
2232
  return () => console.log("[EW SDK] FeedbackButton unmounted");
2191
2233
  }, [debug]);
2234
+ (0, import_react9.useEffect)(() => {
2235
+ const handleResize = () => {
2236
+ setPos((prev) => {
2237
+ const newPos = {
2238
+ edge: prev.edge,
2239
+ y: clampY(prev.y)
2240
+ };
2241
+ savePos(newPos);
2242
+ return newPos;
2243
+ });
2244
+ };
2245
+ window.addEventListener("resize", handleResize);
2246
+ return () => window.removeEventListener("resize", handleResize);
2247
+ }, []);
2248
+ (0, import_react9.useEffect)(() => {
2249
+ return () => {
2250
+ if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
2251
+ };
2252
+ }, []);
2253
+ const handlePointerDown = (0, import_react9.useCallback)((e) => {
2254
+ if (e.button != null && e.button !== 0) return;
2255
+ e.currentTarget.setPointerCapture(e.pointerId);
2256
+ dragStateRef.current = {
2257
+ active: false,
2258
+ startX: e.clientX,
2259
+ startY: e.clientY,
2260
+ currentX: e.clientX,
2261
+ currentY: e.clientY,
2262
+ didDrag: false
2263
+ };
2264
+ }, []);
2265
+ const handlePointerMove = (0, import_react9.useCallback)((e) => {
2266
+ const ds = dragStateRef.current;
2267
+ if (!ds) return;
2268
+ const dx = e.clientX - ds.startX;
2269
+ const dy = e.clientY - ds.startY;
2270
+ const distance = Math.sqrt(dx * dx + dy * dy);
2271
+ if (!ds.active && distance >= DRAG_THRESHOLD) {
2272
+ ds.active = true;
2273
+ ds.didDrag = true;
2274
+ }
2275
+ if (ds.active) {
2276
+ ds.currentX = e.clientX;
2277
+ ds.currentY = e.clientY;
2278
+ const x = e.clientX - BTN_SIZE / 2;
2279
+ const y = clampY(e.clientY - BTN_SIZE / 2);
2280
+ setDragXY({ x, y });
2281
+ }
2282
+ }, []);
2283
+ const handlePointerUp = (0, import_react9.useCallback)((e) => {
2284
+ try {
2285
+ e.currentTarget.releasePointerCapture(e.pointerId);
2286
+ } catch {
2287
+ }
2288
+ const ds = dragStateRef.current;
2289
+ if (!ds || !ds.didDrag) {
2290
+ dragStateRef.current = null;
2291
+ return;
2292
+ }
2293
+ const centerX = ds.currentX;
2294
+ const edge = centerX < window.innerWidth / 2 ? "left" : "right";
2295
+ const y = clampY(ds.currentY - BTN_SIZE / 2);
2296
+ const newPos = { edge, y };
2297
+ setSnapping(true);
2298
+ setPos(newPos);
2299
+ savePos(newPos);
2300
+ setDragXY(null);
2301
+ if (snapTimerRef.current) clearTimeout(snapTimerRef.current);
2302
+ snapTimerRef.current = setTimeout(() => {
2303
+ setSnapping(false);
2304
+ snapTimerRef.current = null;
2305
+ }, 300);
2306
+ }, []);
2192
2307
  if (!ctx) return null;
2193
2308
  const {
2194
2309
  mode,
@@ -2204,9 +2319,13 @@ function FeedbackButton({ position = "bottom-right" }) {
2204
2319
  } = ctx;
2205
2320
  const annotate = mode === "annotate";
2206
2321
  const capabilities = getLauncherCapabilities({ allowPinOnPage: canPinOnPage, allowScreenshot: canScreenshot });
2207
- const posStyle = position === "bottom-left" ? { left: 20, bottom: 20 } : { right: 20, bottom: 20 };
2208
- const hintStyle = position === "bottom-left" ? { left: 56, bottom: 8 } : { right: 56, bottom: 8 };
2209
2322
  const handleToggle = () => {
2323
+ if (dragStateRef.current?.didDrag) {
2324
+ dragStateRef.current.didDrag = false;
2325
+ dragStateRef.current = null;
2326
+ return;
2327
+ }
2328
+ dragStateRef.current = null;
2210
2329
  if (debug) console.log("[EW SDK] handleToggle", { mode, menuOpen });
2211
2330
  setHintDismissed(true);
2212
2331
  if (annotate) {
@@ -2226,12 +2345,44 @@ function FeedbackButton({ position = "bottom-right" }) {
2226
2345
  const capturing = screenshotCapturing;
2227
2346
  const showLauncherHint = showHints && !hintDismissed && !annotate && !menuOpen;
2228
2347
  if (capabilities.actionCount === 0) return null;
2348
+ const restX = snapXForEdge(pos.edge);
2349
+ let wrapperStyle;
2350
+ if (dragXY !== null) {
2351
+ wrapperStyle = {
2352
+ position: "fixed",
2353
+ left: dragXY.x,
2354
+ top: dragXY.y,
2355
+ transition: "none",
2356
+ zIndex: Z.launcher
2357
+ };
2358
+ } else if (snapping) {
2359
+ wrapperStyle = {
2360
+ position: "fixed",
2361
+ left: restX,
2362
+ top: pos.y,
2363
+ transition: SNAP_TRANSITION,
2364
+ zIndex: Z.launcher
2365
+ };
2366
+ } else {
2367
+ wrapperStyle = {
2368
+ position: "fixed",
2369
+ left: restX,
2370
+ top: pos.y,
2371
+ transition: "none",
2372
+ zIndex: Z.launcher
2373
+ };
2374
+ }
2375
+ const hintStyle = pos.edge === "right" ? { right: BTN_SIZE + 8, top: "50%", transform: "translateY(-50%)" } : { left: BTN_SIZE + 8, top: "50%", transform: "translateY(-50%)" };
2376
+ const menuPositionStyle = pos.edge === "right" ? { right: 0, bottom: BTN_SIZE + 4 } : { left: 0, bottom: BTN_SIZE + 4 };
2229
2377
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2230
2378
  "div",
2231
2379
  {
2232
2380
  ref: menuRef,
2233
2381
  "data-ew-feedback-interactive": "true",
2234
- style: { position: "fixed", ...posStyle, zIndex: Z.launcher },
2382
+ style: { ...wrapperStyle, touchAction: "none" },
2383
+ onPointerDown: handlePointerDown,
2384
+ onPointerMove: handlePointerMove,
2385
+ onPointerUp: handlePointerUp,
2235
2386
  children: [
2236
2387
  showLauncherHint && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2237
2388
  "div",
@@ -2258,8 +2409,7 @@ function FeedbackButton({ position = "bottom-right" }) {
2258
2409
  {
2259
2410
  style: {
2260
2411
  position: "absolute",
2261
- bottom: 56,
2262
- right: 0,
2412
+ ...menuPositionStyle,
2263
2413
  marginBottom: 4,
2264
2414
  width: 176,
2265
2415
  borderRadius: 8,
@@ -2324,7 +2474,7 @@ function FeedbackButton({ position = "bottom-right" }) {
2324
2474
  children: [
2325
2475
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react3.Camera, { style: { width: 16, height: 16, color: T.primary, flexShrink: 0 } }),
2326
2476
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
2327
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontWeight: 500 }, children: capturing ? "Capturing\u2026" : "Screenshot" }),
2477
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontWeight: 500 }, children: capturing ? "Capturing..." : "Screenshot" }),
2328
2478
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { fontSize: 10, color: T.muted }, children: "Capture current view" })
2329
2479
  ] })
2330
2480
  ]
@@ -2339,8 +2489,8 @@ function FeedbackButton({ position = "bottom-right" }) {
2339
2489
  onClick: handleToggle,
2340
2490
  style: {
2341
2491
  display: "flex",
2342
- height: 48,
2343
- width: 48,
2492
+ height: BTN_SIZE,
2493
+ width: BTN_SIZE,
2344
2494
  alignItems: "center",
2345
2495
  justifyContent: "center",
2346
2496
  borderRadius: "50%",
@@ -2349,7 +2499,7 @@ function FeedbackButton({ position = "bottom-right" }) {
2349
2499
  border: annotate ? "none" : `1px solid ${T.border}`,
2350
2500
  background: annotate ? T.primary : T.card,
2351
2501
  color: annotate ? T.primaryFg : T.fg,
2352
- cursor: "pointer",
2502
+ cursor: dragXY !== null ? "grabbing" : "pointer",
2353
2503
  transform: annotate ? "scale(1.1)" : "scale(1)"
2354
2504
  },
2355
2505
  title: annotate ? "Exit feedback mode" : "Open feedback menu",