@kelet-ai/feedback-ui 0.6.0 → 0.7.1

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
@@ -193,6 +193,7 @@ Controls how feedback is collected and what data is captured:
193
193
  - **When to use**: Content editing, user interactions, workflow analysis
194
194
  - **Data captured**: State diffs, interaction patterns, timing data
195
195
  - **User experience**: Seamless, no interruption
196
+ - **Smart filtering**: Ignores common loading patterns (null → data) by default
196
197
 
197
198
  ```tsx
198
199
  // Implicit feedback - tracks changes automatically
@@ -201,6 +202,11 @@ const [content, setContent] = useFeedbackState(
201
202
  'content-editor'
202
203
  );
203
204
  // Sends feedback when user stops editing
205
+
206
+ // Loading pattern - no noise generated
207
+ const [user, setUser] = useFeedbackState(null, 'user-data');
208
+ setUser(userData); // ❌ No feedback sent (ignoreInitialNullish: true)
209
+ setUser(updatedUser); // ✅ Feedback sent for real changes
204
210
  ```
205
211
 
206
212
  ### **🏷️ Trigger Names**
@@ -527,14 +533,15 @@ interface FeedbackData {
527
533
 
528
534
  #### **useFeedbackState Options**
529
535
 
530
- | Option | Type | Default | Description |
531
- | ---------------------- | ------------------------------------ | --------------------- | ----------------------------- |
532
- | `debounceMs` | `number` | `1500` | Debounce time in milliseconds |
533
- | `diffType` | `'git' \| 'object' \| 'json'` | `'git'` | Diff output format |
534
- | `compareWith` | `(a: T, b: T) => boolean` | `undefined` | Custom equality function |
535
- | `metadata` | `Record<string, any>` | `{}` | Additional metadata |
536
- | `vote` | `'upvote' \| 'downvote' \| function` | `auto` | Vote determination logic |
537
- | `default_trigger_name` | `string` | `'auto_state_change'` | Default trigger name |
536
+ | Option | Type | Default | Description |
537
+ | ---------------------- | ------------------------------------ | --------------------- | -------------------------------------- |
538
+ | `debounceMs` | `number` | `1500` | Debounce time in milliseconds |
539
+ | `diffType` | `'git' \| 'object' \| 'json'` | `'git'` | Diff output format |
540
+ | `compareWith` | `(a: T, b: T) => boolean` | `undefined` | Custom equality function |
541
+ | `metadata` | `Record<string, any>` | `{}` | Additional metadata |
542
+ | `vote` | `'upvote' \| 'downvote' \| function` | `auto` | Vote determination logic |
543
+ | `default_trigger_name` | `string` | `'auto_state_change'` | Default trigger name |
544
+ | `ignoreInitialNullish` | `boolean` | `true` | Skip null/undefined → data transitions |
538
545
 
539
546
  ---
540
547
 
@@ -8,6 +8,7 @@ interface KeletContextValue {
8
8
  interface KeletProviderProps {
9
9
  apiKey?: string;
10
10
  project: string;
11
+ baseUrl?: string;
11
12
  }
12
13
  export declare const KeletContext: React.Context<KeletContextValue | null>;
13
14
  export declare const useKelet: () => KeletContextValue;
@@ -353,7 +353,7 @@ function requireJsxRuntime() {
353
353
  }
354
354
  var jsxRuntimeExports = requireJsxRuntime();
355
355
  const KeletContext = createContext(null);
356
- const KeletBaseUrl = "https://api.kelet.ai/api";
356
+ const DefaultKeletBaseUrl = "https://api.kelet.ai/api";
357
357
  const useKelet = () => {
358
358
  const context = useContext(KeletContext);
359
359
  if (!context) {
@@ -364,13 +364,16 @@ const useKelet = () => {
364
364
  const useDefaultFeedbackHandler = () => {
365
365
  const context = useContext(KeletContext);
366
366
  if (!context) {
367
+ console.warn(
368
+ "No FeedbackHandler found: defaultFeedbackHandler is not possible since there's no KeletProvider wrapping this call, and handler not provided"
369
+ );
367
370
  return async () => {
368
371
  };
369
372
  } else {
370
373
  return context.feedback;
371
374
  }
372
375
  };
373
- const KeletProvider = ({ apiKey, project, children }) => {
376
+ const KeletProvider = ({ apiKey, project, baseUrl, children }) => {
374
377
  const parentContext = useContext(KeletContext);
375
378
  const resolvedApiKey = apiKey || parentContext?.api_key;
376
379
  if (!resolvedApiKey) {
@@ -379,7 +382,8 @@ const KeletProvider = ({ apiKey, project, children }) => {
379
382
  );
380
383
  }
381
384
  const feedback = async (data) => {
382
- const url = `${KeletBaseUrl}/projects/${project}/feedback`;
385
+ const resolvedBaseUrl = baseUrl || DefaultKeletBaseUrl;
386
+ const url = `${resolvedBaseUrl}/projects/${project}/feedback`;
383
387
  const req = {
384
388
  tx_id: data.tx_id,
385
389
  source: data.source || "EXPLICIT",
@@ -2006,9 +2010,15 @@ function useStateChangeTracking(currentState, tx_id, options) {
2006
2010
  const diffType = options?.diffType ?? "git";
2007
2011
  const compareWith = options?.compareWith;
2008
2012
  const defaultTriggerName = options?.default_trigger_name ?? "auto_state_change";
2013
+ const ignoreInitialNullish = options?.ignoreInitialNullish ?? true;
2009
2014
  const prevStateRef = useRef(currentState);
2010
2015
  const changeStartStateRef = useRef(currentState);
2011
2016
  const isFirstRenderRef = useRef(true);
2017
+ const initialStateRef = useRef(currentState);
2018
+ const hasHadNonNullishStateRef = useRef(
2019
+ currentState != null
2020
+ // != null catches both null and undefined
2021
+ );
2012
2022
  const timeoutRef = useRef(null);
2013
2023
  const currentTriggerNameRef = useRef(void 0);
2014
2024
  const sendFeedback = useCallback(
@@ -2066,6 +2076,17 @@ function useStateChangeTracking(currentState, tx_id, options) {
2066
2076
  const prevState = prevStateRef.current;
2067
2077
  const isEqual = compareWith ? compareWith(prevState, currentState) : JSON.stringify(prevState) === JSON.stringify(currentState);
2068
2078
  if (!isEqual) {
2079
+ const shouldIgnoreChange = ignoreInitialNullish && initialStateRef.current == null && // Initial state was nullish
2080
+ !hasHadNonNullishStateRef.current && // We haven't had non-nullish state before
2081
+ currentState != null;
2082
+ if (currentState != null) {
2083
+ hasHadNonNullishStateRef.current = true;
2084
+ }
2085
+ if (shouldIgnoreChange) {
2086
+ prevStateRef.current = currentState;
2087
+ changeStartStateRef.current = currentState;
2088
+ return;
2089
+ }
2069
2090
  if (!timeoutRef.current) {
2070
2091
  changeStartStateRef.current = prevState;
2071
2092
  }
@@ -2099,6 +2120,7 @@ function useStateChangeTracking(currentState, tx_id, options) {
2099
2120
  debounceMs,
2100
2121
  compareWith,
2101
2122
  defaultTriggerName,
2123
+ ignoreInitialNullish,
2102
2124
  sendFeedback
2103
2125
  ]);
2104
2126
  return {