@uselay/sdk 1.0.0 → 1.1.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/README.md CHANGED
@@ -1,112 +1,279 @@
1
1
  # @uselay/sdk
2
2
 
3
- The feedback layer for React. Drop two components into your app and let teammates or users leave comments pinned to specific UI elements.
3
+ The feedback layer for React. Drop one component into your app and let anyone point at what's wrong and say why no more screenshots in Slack with "the thing on the right."
4
4
 
5
- ## Install
5
+ Comments are pinned to the exact DOM element, not a screenshot coordinate. Your team sees what was clicked, reads the comment, and understands the problem without a follow-up call.
6
+
7
+ ## Quick start
8
+
9
+ ### 1. Install
6
10
 
7
11
  ```bash
8
12
  npm install @uselay/sdk
9
13
  ```
10
14
 
11
- No CSS imports needed styles are injected at runtime.
15
+ No CSS imports needed. Styles are injected at runtime.
12
16
 
13
- ## Quick start
17
+ ### 2. Wrap your app
14
18
 
15
19
  ```tsx
16
- import { LayProvider, LayToggle } from '@uselay/sdk';
20
+ import { LayProvider } from '@uselay/sdk';
17
21
 
18
22
  function App() {
19
23
  return (
20
- <LayProvider
21
- config={{
22
- projectId: 'your-project-id',
23
- user: { id: 'user-1', name: 'Jane' },
24
- }}
25
- >
24
+ <LayProvider projectId="your-project-id">
26
25
  <YourApp />
27
- <LayToggle />
28
26
  </LayProvider>
29
27
  );
30
28
  }
31
29
  ```
32
30
 
33
- Click the toggle, hover to highlight elements, click to pin a comment.
31
+ Get your `projectId` from the [dashboard](https://uselay.com/dashboard). The toggle button and comment UI render automatically inside the provider.
32
+
33
+ Want to identify who's commenting? Add the optional `user` prop:
34
+
35
+ ```tsx
36
+ <LayProvider
37
+ projectId="your-project-id"
38
+ user={{ id: 'user-1', name: 'Jane' }}
39
+ >
40
+ <YourApp />
41
+ </LayProvider>
42
+ ```
43
+
44
+ ### 3. Press C
45
+
46
+ Run your app and press `C` to enter comment mode. Hover to highlight elements. Click to pin a comment.
47
+
48
+ ## How it works
49
+
50
+ 1. **Enter comment mode** — Press `C` or click the toggle button. Elements highlight on hover.
51
+ 2. **Click an element** — A comment input anchors to your selection. The SDK generates a CSS selector and fingerprint to track it.
52
+ 3. **Write your comment** — Type what you see, or tap a starter chip.
53
+ 4. **AI enrichment runs** — Element metadata is captured and enriched with context, intent detection, and suggested actions.
54
+ 5. **Comment appears in the dashboard** — Grouped by page and element, with the AI context card and a viewport screenshot.
55
+ 6. **Your team understands what you mean** — The comment is attached to the element itself. Navigate, resolve, and archive from the dashboard.
34
56
 
35
57
  ## Configuration
36
58
 
37
- Pass a `config` object to `LayProvider`:
59
+ All props are passed directly to `LayProvider`:
38
60
 
39
61
  | Prop | Type | Default | Description |
40
62
  |------|------|---------|-------------|
41
63
  | `projectId` | `string` | *required* | Project ID from the Lay dashboard |
42
- | `user` | `{ id: string; name: string; avatar?: string }` | — | Comment author identity. Omit for anonymous. |
43
- | `userHash` | `string` | — | HMAC-SHA256 hash for verified identity (see below) |
44
- | `adapter` | `LayAdapter` | auto | Custom data adapter. Defaults to hosted backend. |
45
- | `apiUrl` | `string` | `https://uselay.com` | API base URL override |
46
- | `version` | `string` | — | Version tag. Changing this archives previous comments. |
47
- | `ai` | `boolean` | `true` | Enable AI enrichment on comments |
64
+ | `user` | `{ id, name, avatar? }` | — | Comment author identity. Omit for anonymous. |
65
+ | `userHash` | `string` | — | HMAC-SHA256 hash for verified identity. Use `identifyUser()` to generate. |
48
66
  | `mode` | `'review' \| 'support'` | dashboard setting | Override project mode |
49
67
  | `active` | `boolean` | dashboard setting | Override active state. `false` = widget hidden. |
50
- | `starterChips` | `StarterChip[]` | `["Visual bug", "Copy issue", "Love this"]` | Quick-feedback chips. `[]` to disable. |
68
+ | `ai` | `boolean` | `true` | Enable AI enrichment on comments |
51
69
  | `screenshots` | `boolean` | `true` | Capture viewport screenshots on comment creation |
70
+ | `starterChips` | `StarterChip[]` | `["Visual bug", "Copy issue", "Love this"]` | Quick-feedback chips. `[]` to disable. |
71
+ | `version` | `string` | — | Version tag. Changing this archives previous comments. |
72
+ | `adapter` | `LayAdapter` | hosted | Custom data adapter. Defaults to hosted backend. |
73
+ | `apiUrl` | `string` | `https://uselay.com` | API base URL override |
74
+
75
+ Most projects need only `projectId`.
76
+
77
+ ## Modes
78
+
79
+ **Review** — Team feedback on prototypes and staging. Comments are threaded. AI generates element descriptions and fix suggestions. Best for staging URLs, design reviews, QA passes.
80
+
81
+ ```tsx
82
+ <LayProvider projectId="..." mode="review">
83
+ <StagingApp />
84
+ </LayProvider>
85
+ ```
86
+
87
+ **Support** — End-user feedback on live sites. Comments are standalone. AI detects intent (bug report, confusion, feature request, praise). Best for production apps, beta programs, user research.
88
+
89
+ ```tsx
90
+ <LayProvider projectId="..." mode="support">
91
+ <ProductionApp />
92
+ </LayProvider>
93
+ ```
52
94
 
53
95
  ## Identified users
54
96
 
55
- For verified identity in production, hash the user ID server-side:
97
+ Use `identifyUser` to verify commenter identity server-side. It returns spread-friendly props — `{ user, userHash }` for a valid user, or `{}` for null/undefined input.
56
98
 
57
99
  ```ts
58
- // Server (Next.js API route, Express, etc.)
59
- import { createUserHash } from '@uselay/sdk/server';
100
+ import { identifyUser } from '@uselay/sdk/server';
60
101
 
61
102
  // Set LAY_SECRET_KEY in your environment (from dashboard → project settings)
62
- const hash = createUserHash(user.id);
103
+ const identified = identifyUser(session?.user);
104
+ // → { user: { id, name, avatar }, userHash: '...' } or {}
63
105
  ```
64
106
 
65
- Then pass both `user` and `userHash` to the provider:
107
+ Spread the result into the provider:
66
108
 
67
109
  ```tsx
68
- <LayProvider config={{ projectId: '...', user, userHash: hash }}>
110
+ <LayProvider projectId="..." {...identifyUser(session?.user)}>
111
+ <YourApp />
112
+ </LayProvider>
69
113
  ```
70
114
 
71
- ## Starter chips
115
+ Never expose `LAY_SECRET_KEY` to the client. `identifyUser` must run on your server.
72
116
 
73
- Customize the quick-feedback chips:
117
+ ## Stable anchors
118
+
119
+ Comments are pinned to DOM elements via CSS selectors. The SDK resolves elements in three layers:
120
+
121
+ 1. **`data-feedback-id`** — If present, always used. Most stable. Add to elements that receive frequent feedback.
122
+ 2. **CSS selector** — Generated from IDs, `data-testid`, semantic attributes. Skips auto-generated class names.
123
+ 3. **Element fingerprint** — Hash of tag, text, attributes, position. Used as fallback when the selector stops matching.
74
124
 
75
125
  ```tsx
76
- <LayProvider
77
- config={{
78
- projectId: '...',
79
- starterChips: [
80
- { label: 'Broken', value: 'This element is broken' },
81
- { label: 'Wrong copy' },
82
- { label: 'Looks great' },
83
- ],
84
- }}
85
- >
126
+ <button data-feedback-id="checkout-submit">Place Order</button>
86
127
  ```
87
128
 
88
- ## Screenshots
89
-
90
- Viewport screenshots are captured automatically when a comment is created. Disable with `screenshots: false`.
129
+ If a comment can't be resolved to any element, it appears in the Detached Comments panel in the dashboard.
91
130
 
92
131
  ## Custom adapter
93
132
 
94
133
  Implement the `LayAdapter` interface to use your own backend:
95
134
 
96
- ```tsx
97
- import { LayProvider } from '@uselay/sdk';
135
+ ```ts
98
136
  import type { LayAdapter } from '@uselay/sdk';
99
137
 
100
138
  const myAdapter: LayAdapter = {
101
- getComments: async (projectId, urlPath) => { /* ... */ },
102
- addComment: async (comment) => { /* ... */ },
103
- subscribe: (projectId, urlPath, callback) => { /* ... */ },
104
- // ...
139
+ getConfig: async (projectId) => { /* ... */ },
140
+ getComments: async (projectId, urlPath, options) => { /* ... */ },
141
+ addComment: async (comment, options) => { /* ... */ },
142
+ updateComment: async (id, update, options) => { /* ... */ },
143
+ uploadScreenshot: async (projectId, commentId, blob, bounds, options) => { /* ... */ },
144
+ subscribe: (projectId, callback, options) => { /* return unsubscribe fn */ },
105
145
  };
146
+ ```
106
147
 
107
- <LayProvider config={{ projectId: '...', adapter: myAdapter }}>
148
+ ```tsx
149
+ <LayProvider projectId="..." adapter={myAdapter}>
150
+ <YourApp />
151
+ </LayProvider>
108
152
  ```
109
153
 
154
+ The SDK also exports `createMemoryAdapter()` for testing and prototyping.
155
+
156
+ ## Keyboard shortcuts
157
+
158
+ | Key | Action |
159
+ |-----|--------|
160
+ | `C` | Toggle comment mode |
161
+ | `Escape` | Exit comment mode / dismiss input |
162
+
163
+ Shortcuts are disabled when focus is inside an input, textarea, or contenteditable element.
164
+
165
+ ## Framework examples
166
+
167
+ ### Next.js App Router
168
+
169
+ ```tsx
170
+ // app/layout.tsx
171
+ import { identifyUser } from '@uselay/sdk/server';
172
+ import { LayProvider } from '@uselay/sdk';
173
+
174
+ export default async function RootLayout({ children }) {
175
+ const session = await auth();
176
+
177
+ return (
178
+ <html lang="en">
179
+ <body>
180
+ <LayProvider
181
+ projectId={process.env.NEXT_PUBLIC_LAY_PROJECT_ID!}
182
+ {...identifyUser(session?.user)}
183
+ >
184
+ {children}
185
+ </LayProvider>
186
+ </body>
187
+ </html>
188
+ );
189
+ }
190
+ ```
191
+
192
+ ### Next.js Pages Router
193
+
194
+ ```tsx
195
+ // pages/_app.tsx
196
+ import { identifyUser } from '@uselay/sdk/server';
197
+ import { LayProvider } from '@uselay/sdk';
198
+
199
+ export async function getServerSideProps({ req }) {
200
+ const session = await getSession(req);
201
+ return { props: { identified: identifyUser(session?.user) } };
202
+ }
203
+
204
+ export default function App({ Component, pageProps }) {
205
+ return (
206
+ <LayProvider
207
+ projectId={process.env.NEXT_PUBLIC_LAY_PROJECT_ID!}
208
+ {...pageProps.identified}
209
+ >
210
+ <Component {...pageProps} />
211
+ </LayProvider>
212
+ );
213
+ }
214
+ ```
215
+
216
+ ### Vite + React
217
+
218
+ ```tsx
219
+ // src/main.tsx
220
+ import { LayProvider } from '@uselay/sdk';
221
+ import App from './App';
222
+
223
+ ReactDOM.createRoot(document.getElementById('root')!).render(
224
+ <LayProvider projectId={import.meta.env.VITE_LAY_PROJECT_ID}>
225
+ <App />
226
+ </LayProvider>
227
+ );
228
+ ```
229
+
230
+ ### Remix
231
+
232
+ ```tsx
233
+ // app/root.tsx
234
+ import { LayProvider } from '@uselay/sdk';
235
+ import { identifyUser } from '@uselay/sdk/server';
236
+
237
+ export async function loader({ request }) {
238
+ const user = await getUser(request);
239
+ return json({
240
+ layProjectId: process.env.LAY_PROJECT_ID,
241
+ ...identifyUser(user),
242
+ });
243
+ }
244
+
245
+ export default function Root() {
246
+ const { layProjectId, user, userHash } = useLoaderData();
247
+ return (
248
+ <html lang="en">
249
+ <body>
250
+ <LayProvider projectId={layProjectId!} user={user} userHash={userHash}>
251
+ <Outlet />
252
+ </LayProvider>
253
+ </body>
254
+ </html>
255
+ );
256
+ }
257
+ ```
258
+
259
+ ## Troubleshooting
260
+
261
+ **Widget does not appear** — Check that `LayProvider` wraps your app with a valid `projectId`. The project must be active in the dashboard (or pass the `active` prop).
262
+
263
+ **Comments disappear after deploy** — CSS selectors changed. Add `data-feedback-id` to critical elements. Detached comments appear in the Detached Comments panel.
264
+
265
+ **AI context cards not showing** — Verify `ai` is not set to `false`. AI processing takes a few seconds after comment creation.
266
+
267
+ **`identifyUser` throws "LAY_SECRET_KEY not set"** — Set the `LAY_SECRET_KEY` environment variable on your server. Never expose it client-side.
268
+
269
+ **Widget re-renders frequently** — If you pass object props like `user` inline on every render, it creates a new reference each time. Define objects outside the component or use `useMemo`.
270
+
271
+ ## Next steps
272
+
273
+ - Open the [dashboard](https://uselay.com/dashboard) and create your first project
274
+ - Invite your team from project settings
275
+ - Add `data-feedback-id` to your most important UI elements
276
+
110
277
  ## License
111
278
 
112
279
  MIT
package/dist/index.d.mts CHANGED
@@ -31,8 +31,14 @@ interface ElementMetadata {
31
31
  device: string;
32
32
  fingerprint?: ElementFingerprint;
33
33
  }
34
+ interface DeveloperTriage {
35
+ element_summary: string;
36
+ whats_happening: string;
37
+ likely_causes: string[];
38
+ where_to_look: string[];
39
+ }
34
40
  interface AIContextReview {
35
- ai_context_version: 2 | 3;
41
+ ai_context_version: 2 | 3 | 4;
36
42
  mode: 'review';
37
43
  category: 'visual' | 'accessibility' | 'layout' | 'copy' | 'interaction';
38
44
  /** v3: single interpretation sentence replacing suggestions + accessibility_issues */
@@ -46,12 +52,15 @@ interface AIContextReview {
46
52
  enriched_at: string;
47
53
  }
48
54
  interface AIContextSupport {
49
- ai_context_version: 2 | 3;
55
+ ai_context_version: 2 | 3 | 4;
50
56
  mode: 'support';
51
57
  intent: 'confusion' | 'bug_report' | 'feature_request' | 'complaint' | 'question' | 'praise' | 'other';
52
58
  urgency: 'low' | 'medium' | 'high';
53
59
  summary: string;
54
- suggested_response: string;
60
+ /** v4: developer triage for bug_report/confusion intents */
61
+ developer_triage?: DeveloperTriage | null;
62
+ /** @deprecated v3 only — removed in v4 */
63
+ suggested_response?: string;
55
64
  /** @deprecated v2 only — removed in v3 */
56
65
  confidence?: number;
57
66
  enriched_at: string;
package/dist/index.d.ts CHANGED
@@ -31,8 +31,14 @@ interface ElementMetadata {
31
31
  device: string;
32
32
  fingerprint?: ElementFingerprint;
33
33
  }
34
+ interface DeveloperTriage {
35
+ element_summary: string;
36
+ whats_happening: string;
37
+ likely_causes: string[];
38
+ where_to_look: string[];
39
+ }
34
40
  interface AIContextReview {
35
- ai_context_version: 2 | 3;
41
+ ai_context_version: 2 | 3 | 4;
36
42
  mode: 'review';
37
43
  category: 'visual' | 'accessibility' | 'layout' | 'copy' | 'interaction';
38
44
  /** v3: single interpretation sentence replacing suggestions + accessibility_issues */
@@ -46,12 +52,15 @@ interface AIContextReview {
46
52
  enriched_at: string;
47
53
  }
48
54
  interface AIContextSupport {
49
- ai_context_version: 2 | 3;
55
+ ai_context_version: 2 | 3 | 4;
50
56
  mode: 'support';
51
57
  intent: 'confusion' | 'bug_report' | 'feature_request' | 'complaint' | 'question' | 'praise' | 'other';
52
58
  urgency: 'low' | 'medium' | 'high';
53
59
  summary: string;
54
- suggested_response: string;
60
+ /** v4: developer triage for bug_report/confusion intents */
61
+ developer_triage?: DeveloperTriage | null;
62
+ /** @deprecated v3 only — removed in v4 */
63
+ suggested_response?: string;
55
64
  /** @deprecated v2 only — removed in v3 */
56
65
  confidence?: number;
57
66
  enriched_at: string;