@sylergydigital/issue-pin-sdk 0.6.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/LICENSE ADDED
@@ -0,0 +1,74 @@
1
+ Issue Pin SDK Proprietary License
2
+
3
+ Copyright (c) 2026 Sylergy Digital. All rights reserved.
4
+
5
+ Permission is granted to any person or entity obtaining a copy of this software
6
+ and associated documentation files (the "Software") to use the Software solely
7
+ as compiled code or as installed from an authorized package registry, subject to
8
+ the conditions below.
9
+
10
+ 1. Permitted Use
11
+
12
+ You may:
13
+
14
+ - use the Software internally within your own applications and systems;
15
+ - integrate the unmodified Software into applications you own or operate;
16
+ - make a reasonable number of copies as necessary for backup, deployment, and
17
+ internal development;
18
+ - modify the Software only for your own internal use, provided such
19
+ modifications are not distributed or disclosed outside your organization.
20
+
21
+ 2. Restrictions
22
+
23
+ You may not:
24
+
25
+ - sell, sublicense, redistribute, publish, or otherwise make the Software
26
+ available to any third party except as embedded in your own application in a
27
+ manner that does not expose the Software itself for reuse;
28
+ - distribute source code of the Software, including modified versions;
29
+ - offer the Software on a standalone basis;
30
+ - use the Software to provide a competing SDK, library, or hosted service;
31
+ - remove or alter any copyright, trademark, or proprietary notices;
32
+ - claim that the Software is open source.
33
+
34
+ 3. Ownership
35
+
36
+ The Software is licensed, not sold. Sylergy Digital and its licensors retain
37
+ all right, title, and interest in and to the Software, including all
38
+ intellectual property rights.
39
+
40
+ 4. Feedback
41
+
42
+ If you provide feedback, suggestions, or ideas regarding the Software, Sylergy
43
+ Digital may use them without restriction or obligation to you.
44
+
45
+ 5. No Warranty
46
+
47
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
48
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
49
+ FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT.
50
+
51
+ 6. Limitation of Liability
52
+
53
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, SYLERGY DIGITAL WILL NOT BE LIABLE FOR
54
+ ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE
55
+ DAMAGES, OR FOR ANY LOSS OF DATA, PROFITS, REVENUE, OR BUSINESS OPPORTUNITY,
56
+ ARISING OUT OF OR RELATED TO THE SOFTWARE OR THIS LICENSE, EVEN IF ADVISED OF
57
+ THE POSSIBILITY OF SUCH DAMAGES. IN NO EVENT WILL SYLERGY DIGITAL'S TOTAL
58
+ LIABILITY EXCEED ONE HUNDRED U.S. DOLLARS (USD $100).
59
+
60
+ 7. Termination
61
+
62
+ This license terminates automatically if you violate any term of this license.
63
+ Upon termination, you must stop using the Software and destroy all copies in
64
+ your possession or control, except where retention is required by law.
65
+
66
+ 8. Governing Law
67
+
68
+ This license is governed by the laws of the jurisdiction in which Sylergy
69
+ Digital is established, excluding conflict-of-law rules.
70
+
71
+ 9. Contact
72
+
73
+ For commercial licensing, redistribution, or other permissions, contact
74
+ Sylergy Digital.
package/README.md ADDED
@@ -0,0 +1,376 @@
1
+ # @sylergydigital/issue-pin-sdk
2
+
3
+ In-app visual feedback SDK for Issue Pin.
4
+
5
+ ## Installation
6
+
7
+ ### 1. Install
8
+
9
+ ```bash
10
+ npm install @sylergydigital/issue-pin-sdk
11
+ ```
12
+
13
+ ### 2. Peer dependencies
14
+
15
+ Ensure these are installed in your app:
16
+
17
+ ```bash
18
+ npm install react react-dom @supabase/supabase-js html2canvas lucide-react
19
+ ```
20
+
21
+ The public GitHub repo at `sylergydigital/issue-pin-sdk` is a read-only mirror. Source changes should be made in the private source repo and will sync across automatically.
22
+
23
+ `react-router-dom` is **not** required. The SDK tracks `window.location` for thread fetching and `?highlight_thread=` without router context.
24
+
25
+ ## Usage
26
+
27
+ The SDK is **disabled by default**. Pass `enabled={true}` (or bind it to state) to activate the feedback UI.
28
+
29
+ ```tsx
30
+ import { useState } from "react";
31
+ import { IssuePin } from "@sylergydigital/issue-pin-sdk";
32
+ import { supabase } from "./lib/supabase"; // your app's Supabase client
33
+
34
+ function App() {
35
+ const [feedbackOn, setFeedbackOn] = useState(false);
36
+
37
+ return (
38
+ <>
39
+ <nav>
40
+ <button onClick={() => setFeedbackOn(f => !f)}>
41
+ 💬 Feedback
42
+ </button>
43
+ </nav>
44
+
45
+ <Home />
46
+
47
+ {/* Recommended: pass supabaseClient for automatic user identity */}
48
+ <IssuePin
49
+ apiKey="ew_live_..."
50
+ enabled={feedbackOn}
51
+ supabaseClient={supabase}
52
+ />
53
+ </>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ## Props
59
+
60
+ | Prop | Type | Default | Description |
61
+ |------|------|---------|-------------|
62
+ | `apiKey` | `string` | — | **Required.** Your workspace API key |
63
+ | `enabled` | `boolean` | `false` | Toggle feedback UI visibility without unmounting |
64
+ | `supabaseClient` | `SupabaseClient` | — | **Recommended.** Your app's Supabase client — auto-extracts user identity from the auth session and auto-federates users into the workspace |
65
+ | `user.id` | `string` | — | Manual user ID for attribution (overrides auto-detect) |
66
+ | `user.email` | `string` | — | Manual user email (overrides auto-detect) |
67
+ | `user.displayName` | `string` | — | Name shown on comments (overrides auto-detect) |
68
+ | `allowPinOnPage` | `boolean` | `true` | Allow creating new element-based pins from the launcher |
69
+ | `allowScreenshot` | `boolean` | `true` | Allow screenshot capture from the launcher |
70
+ | `showHints` | `boolean` | `true` | Show SDK-owned instructional hints and coachmarks, including the "Open Issue Pin" toast |
71
+ | `scrollContainer` | `{ key: string; ref: RefObject<HTMLElement> }` | — | Store and render new element pins in a host-owned scroll container |
72
+ | `resolveAnchor` | `(target: Element) => { key: string; selector?: string \| null } \| null` | — | Resolve a stable logical anchor key for clicked elements |
73
+ | `pinsVisible` | `boolean` | `true` | Show historical thread pins independently from the rest of the SDK UI |
74
+ | `buttonPosition` | `"bottom-right" \| "bottom-left"` | `"bottom-right"` | Floating button position |
75
+ | `renderLauncher` | `(props) => ReactNode` | — | Replace the stock launcher while keeping SDK-owned state/actions |
76
+ | `renderModalCaptureButton` | `(props) => ReactNode` | — | Replace the default screenshot button injected into open dialogs |
77
+ | `showModalCaptureButton` | `boolean` | `true` | Control whether modal screenshot buttons are injected |
78
+ | `mode` | `"view" \| "annotate"` | — | **Controlled** annotate mode — use with `onModeChange` |
79
+ | `onModeChange` | `(mode: FeedbackMode) => void` | — | Called when annotate mode changes |
80
+ | `feedbackActive` | `boolean` | — | **Deprecated.** Use `mode`: `annotate` ↔ `true`, `view` ↔ `false` |
81
+ | `onFeedbackActiveChange` | `(active: boolean) => void` | — | **Deprecated.** Use `onModeChange` |
82
+
83
+ ### Migration (v0.x → coordinate-first pins)
84
+
85
+ - `x_position` / `y_position` remain the persisted source of truth.
86
+ - Element pins now resolve in this order: `anchor_key` → `selector` → stored coordinates.
87
+ - Without `scrollContainer`, element pins stay document-relative. With `scrollContainer`, new element pins are stored and rendered in that container's coordinate space.
88
+ - Prefer **`mode` / `onModeChange`** instead of **`feedbackActive` / `onFeedbackActiveChange`** for clarity.
89
+ - No `<BrowserRouter>` wrapper is required for the SDK. The SDK listens to `history.pushState` / `replaceState` and `popstate` so thread fetching and `?highlight_thread=` stay in sync in SPAs without importing React Router.
90
+
91
+ ### TypeScript
92
+
93
+ ```ts
94
+ import type { FeedbackMode } from "@sylergydigital/issue-pin-sdk";
95
+ // FeedbackMode = "view" | "annotate"
96
+ ```
97
+
98
+ ### Logical anchors in a scroll container
99
+
100
+ ```tsx
101
+ import { useRef } from "react";
102
+ import { IssuePin, useIssuePinAnchor } from "@sylergydigital/issue-pin-sdk";
103
+
104
+ function VisitRow({ visit }: { visit: { id: string; name: string } }) {
105
+ const ref = useIssuePinAnchor(`visit-row:${visit.id}`);
106
+ return <div ref={ref} data-visit-id={visit.id}>{visit.name}</div>;
107
+ }
108
+
109
+ function AppShell() {
110
+ const mainRef = useRef<HTMLDivElement>(null);
111
+
112
+ return (
113
+ <>
114
+ <div ref={mainRef} style={{ overflow: "auto", height: 600 }}>
115
+ <VisitRow visit={{ id: "123", name: "Annual review" }} />
116
+ </div>
117
+
118
+ <IssuePin
119
+ apiKey="ew_live_..."
120
+ enabled
121
+ scrollContainer={{ key: "dashboard-main", ref: mainRef }}
122
+ resolveAnchor={(target) => {
123
+ const row = target.closest("[data-visit-id]");
124
+ if (!row) return null;
125
+ return {
126
+ key: `visit-row:${row.getAttribute("data-visit-id")}`,
127
+ };
128
+ }}
129
+ />
130
+ </>
131
+ );
132
+ }
133
+ ```
134
+
135
+ ### Layer stacking (z-index)
136
+
137
+ UI layers use a single internal scale exported as **`Z`** (pins → overlay → popover → launcher → screenshot modal → flash). If your app uses very high `z-index` values (e.g. `99999` modals), raise them above `Z.launcher` (or import `Z` and position relative to it):
138
+
139
+ ```ts
140
+ import { Z } from "@sylergydigital/issue-pin-sdk";
141
+
142
+ // Example: host modal above the SDK launcher
143
+ <div style={{ zIndex: Z.launcher + 1 }}>...</div>
144
+ ```
145
+
146
+ ## Controlling visibility
147
+
148
+ Since `enabled` defaults to `false`, the SDK mounts its provider (preserving state and subscriptions) but renders no UI until activated. Common patterns:
149
+
150
+ ### Navbar toggle (recommended)
151
+
152
+ ```tsx
153
+ const [feedbackOn, setFeedbackOn] = useState(false);
154
+
155
+ <button onClick={() => setFeedbackOn(f => !f)}>Feedback</button>
156
+ <IssuePin apiKey="ew_live_..." enabled={feedbackOn} />
157
+ ```
158
+
159
+ ### Role-based
160
+
161
+ ```tsx
162
+ {["qa", "admin"].includes(currentUser.role) && (
163
+ <IssuePin apiKey="ew_live_..." enabled={feedbackOn} user={currentUser} />
164
+ )}
165
+ ```
166
+
167
+ ### Environment-based
168
+
169
+ ```tsx
170
+ {import.meta.env.VITE_APP_ENV !== "production" && (
171
+ <IssuePin apiKey="ew_live_..." enabled={feedbackOn} />
172
+ )}
173
+ ```
174
+
175
+ ## Advanced usage
176
+
177
+ Use the built-in customization props first. Composing low-level SDK internals directly is no longer the recommended way to customize launcher behavior.
178
+
179
+ ### Launcher action flags
180
+
181
+ ```tsx
182
+ <IssuePin
183
+ apiKey="ew_live_..."
184
+ supabaseClient={supabase}
185
+ enabled={feedbackOn}
186
+ allowPinOnPage={false}
187
+ />
188
+ ```
189
+
190
+ If only one launcher action is enabled, the stock launcher triggers it directly instead of opening the menu.
191
+
192
+ ### Compose feature flags
193
+
194
+ ```tsx
195
+ <IssuePin
196
+ apiKey="ew_live_..."
197
+ enabled={feedbackEnabled}
198
+ allowPinOnPage={pinOnPageEnabled}
199
+ allowScreenshot={screenshotEnabled}
200
+ pinsVisible={pinsVisible}
201
+ showHints={false}
202
+ />
203
+ ```
204
+
205
+ ### Hide historical pins without disabling feedback
206
+
207
+ ```tsx
208
+ <IssuePin
209
+ apiKey="ew_live_..."
210
+ supabaseClient={supabase}
211
+ enabled
212
+ pinsVisible={showHistoricalPins}
213
+ />
214
+ ```
215
+
216
+ ### Custom launcher
217
+
218
+ ```tsx
219
+ <IssuePin
220
+ apiKey="ew_live_..."
221
+ supabaseClient={supabase}
222
+ enabled
223
+ renderLauncher={({ mode, canPinOnPage, canScreenshot, toggleMenu, enterPinMode, exitPinMode, startScreenshotCapture }) => (
224
+ <div style={{ position: "fixed", right: 24, bottom: 24 }}>
225
+ {mode === "annotate" ? (
226
+ <button onClick={exitPinMode}>Exit pin mode</button>
227
+ ) : canPinOnPage ? (
228
+ <button onClick={enterPinMode}>Pin on page</button>
229
+ ) : canScreenshot ? (
230
+ <button onClick={() => void startScreenshotCapture()}>Screenshot</button>
231
+ ) : (
232
+ <button onClick={toggleMenu}>Feedback</button>
233
+ )}
234
+ </div>
235
+ )}
236
+ />
237
+ ```
238
+
239
+ ### Custom modal screenshot button
240
+
241
+ ```tsx
242
+ <IssuePin
243
+ apiKey="ew_live_..."
244
+ supabaseClient={supabase}
245
+ enabled
246
+ renderModalCaptureButton={({ captureScreenshot }) => (
247
+ <button
248
+ onClick={() => void captureScreenshot()}
249
+ style={{ position: "absolute", top: 12, right: 12 }}
250
+ >
251
+ Capture
252
+ </button>
253
+ )}
254
+ />
255
+ ```
256
+
257
+ ### Low-level composition (advanced only)
258
+
259
+ Import individual components only if you need full custom assembly:
260
+
261
+ ```tsx
262
+ import { FeedbackProvider, FeedbackButton, FeedbackOverlay, ThreadPins } from "@sylergydigital/issue-pin-sdk";
263
+
264
+ function App() {
265
+ return (
266
+ <FeedbackProvider apiKey="ew_live_...">
267
+ <YourApp />
268
+ <FeedbackOverlay />
269
+ <ThreadPins />
270
+ <FeedbackButton />
271
+ </FeedbackProvider>
272
+ );
273
+ }
274
+ ```
275
+
276
+ `ThreadPins` renders markers at stored document-percentage coordinates and updates on scroll/resize (no CSS selector resolution).
277
+
278
+ ## User identity & auto-federation
279
+
280
+ ### Supabase auth (recommended — zero config)
281
+
282
+ If your app uses Supabase for authentication, just pass your existing client. The SDK:
283
+ 1. **Auto-detects identity** from `auth.getSession()` — no manual user props needed
284
+ 2. **Auto-federates** the user into the Issue Pin workspace as a `commenter` — they appear in the workspace member list and their comments are properly attributed
285
+ 3. **Uses the dashboard org `site_url`** for thread deep-links when a user clicks an in-app pin
286
+
287
+ ```tsx
288
+ import { supabase } from "./lib/supabase";
289
+
290
+ <IssuePin apiKey="ew_live_..." supabaseClient={supabase} enabled={feedbackOn} />
291
+ ```
292
+
293
+ No backend code, edge functions, or sync secrets needed. The SDK handles everything transparently.
294
+
295
+ ### How auto-federation works
296
+
297
+ When a user is detected via `supabaseClient`:
298
+ 1. The SDK calls the `sdk-federate` endpoint with the API key + user identity
299
+ 2. The endpoint validates the API key, resolves the workspace, and upserts the user as a `client_federated` workspace member with `commenter` role
300
+ 3. An identity source (`sdk:<workspace-slug>`) is auto-created if it doesn't exist
301
+
302
+ ```mermaid
303
+ sequenceDiagram
304
+ participant App as Host App
305
+ participant SDK as IssuePin SDK
306
+ participant EF as sdk-federate (Edge Fn)
307
+ participant DB as Database
308
+
309
+ App->>SDK: Mount with apiKey + supabaseClient
310
+ SDK->>SDK: auth.getSession() → extract id, email, name
311
+ SDK->>EF: POST { apiKey, externalId, email, displayName }
312
+ EF->>DB: SHA-256(apiKey) → resolve_api_key()
313
+ DB-->>EF: workspace_id
314
+ EF->>DB: Upsert user_identities
315
+ EF->>DB: Upsert workspace_members (commenter role)
316
+ DB-->>EF: OK
317
+ EF-->>SDK: { success, user_id, workspace_id }
318
+ SDK-->>App: User can now post feedback
319
+ Note over SDK: Result cached — runs once per session
320
+ ```
321
+
322
+ ### Other auth providers (manual)
323
+
324
+ For non-Supabase auth systems, pass identity props directly:
325
+
326
+ ```tsx
327
+ // NextAuth / Auth.js
328
+ const { data: session } = useSession();
329
+ <IssuePin apiKey="ew_live_..." user={{ email: session?.user?.email, displayName: session?.user?.name }} />
330
+ ```
331
+
332
+ ```tsx
333
+ // Clerk
334
+ const { user } = useUser();
335
+ <IssuePin apiKey="ew_live_..." user={{ id: user?.id, email: user?.primaryEmailAddress?.emailAddress, displayName: user?.fullName }} />
336
+ ```
337
+
338
+ If neither `supabaseClient` nor `user` props are provided, a console warning will remind you during development.
339
+
340
+ ### Server-to-server federation (advanced)
341
+
342
+ For bulk provisioning or non-Supabase apps, use the `federate-user` edge function with a `sync_secret`. See the [Federation docs](../docs/features/feature-poor-mans-federation.md) for details.
343
+
344
+ ## Security
345
+
346
+ The API key is a **publishable key** — safe to include in client-side code. It only grants scoped access to create threads and comments for the associated workspace, enforced server-side via Row Level Security.
347
+
348
+ ## AI Agent Prompt
349
+
350
+ Paste this into **Claude Code**, **Cursor**, or **Codex** to integrate the SDK automatically:
351
+
352
+ ```
353
+ Add the Issue Pin feedback SDK to this React app.
354
+
355
+ Install: npm install @sylergydigital/issue-pin-sdk
356
+ Peer deps: npm install @supabase/supabase-js html2canvas lucide-react
357
+
358
+ .npmrc setup (required for GitHub Packages):
359
+ //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
360
+ @sylergydigital:registry=https://npm.pkg.github.com
361
+
362
+ Integration:
363
+ 1. Import { IssuePin } from "@sylergydigital/issue-pin-sdk"
364
+ 2. Add a boolean state: const [feedbackOn, setFeedbackOn] = useState(false)
365
+ 3. Place <IssuePin apiKey={import.meta.env.VITE_ISSUE_PIN_API_KEY} enabled={feedbackOn} supabaseClient={supabase} /> in your app tree (e.g. layout root)
366
+ 4. Add a toggle button: <button onClick={() => setFeedbackOn(f => !f)}>💬 Feedback</button>
367
+ 5. Import your app's Supabase client: import { supabase } from "./lib/supabase"
368
+
369
+ Constraints:
370
+ - enabled defaults to false; SDK mounts but renders no UI until true
371
+ - apiKey is a publishable key (safe for client-side code)
372
+ - Pass supabaseClient for automatic user identity (recommended for Supabase auth apps)
373
+ - Alternative: pass user={{ email: "user@example.com", displayName: "Name" }} for non-Supabase auth
374
+ - Optional feature flags: allowPinOnPage, allowScreenshot, pinsVisible, showHints
375
+ - Use showHints={false} if the host app wants to suppress SDK instructional coachmarks such as the "Open Issue Pin" toast
376
+ ```