featuredrop 2.6.1 → 2.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.
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "featuredrop",
3
+ "description": "AI-assisted product adoption setup — changelogs, tours, checklists, badges, feedback widgets. Helps Claude Code configure FeatureDrop in any project.",
4
+ "version": "2.5.0",
5
+ "author": {
6
+ "name": "GLINR STUDIOS",
7
+ "url": "https://glincker.com"
8
+ },
9
+ "homepage": "https://featuredrop.dev",
10
+ "repository": "https://github.com/GLINCKER/featuredrop",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "feature-discovery",
14
+ "changelog",
15
+ "product-tours",
16
+ "onboarding",
17
+ "saas",
18
+ "react",
19
+ "developer-tools"
20
+ ]
21
+ }
package/README.md CHANGED
@@ -223,18 +223,84 @@ npx featuredrop migrate --from launchnotes --input launchnotes-export.json
223
223
 
224
224
  ---
225
225
 
226
- ## React Hooks
226
+ ## Headless Hooks (for shadcn / custom UI)
227
227
 
228
- | Hook | Returns |
229
- |---|---|
230
- | `useFeatureDrop()` | Full context: features, count, dismiss, throttle controls |
231
- | `useNewFeature(key)` | Single nav item: `{ isNew, feature, dismiss }` |
232
- | `useNewCount()` | Current unread badge count |
233
- | `useTour(id)` | Imperative tour controls and step snapshot |
234
- | `useTourSequencer(sequence)` | Ordered multi-tour orchestration |
235
- | `useChecklist(id)` | Checklist progress + task controls |
236
- | `useSurvey(id)` | Survey controls: `show`, `hide`, `askLater` |
237
- | `useTabNotification()` | Browser tab title count: `"(3) My App"` |
228
+ Don't want our components? Use hooks — **data + actions, zero JSX**:
229
+
230
+ ```tsx
231
+ import { useChangelog } from 'featuredrop/react/hooks'
232
+ import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
233
+ import { Badge } from '@/components/ui/badge'
234
+
235
+ function MyChangelog() {
236
+ const { newFeatures, newCount, dismiss, markAllSeen } = useChangelog()
237
+
238
+ return (
239
+ <Sheet onOpenChange={() => markAllSeen()}>
240
+ <SheetTrigger>
241
+ What's New {newCount > 0 && <Badge>{newCount}</Badge>}
242
+ </SheetTrigger>
243
+ <SheetContent>
244
+ {newFeatures.map(f => (
245
+ <div key={f.id} onClick={() => dismiss(f.id)}>
246
+ <h3>{f.label}</h3>
247
+ <p>{f.description}</p>
248
+ </div>
249
+ ))}
250
+ </SheetContent>
251
+ </Sheet>
252
+ )
253
+ }
254
+ ```
255
+
256
+ | Hook | Import | Returns |
257
+ |---|---|---|
258
+ | `useFeatureDrop()` | `featuredrop/react/hooks` | Full context: features, count, dismiss, throttle controls |
259
+ | `useNewFeature(key)` | `featuredrop/react/hooks` | `{ isNew, feature, dismiss }` |
260
+ | `useNewCount()` | `featuredrop/react/hooks` | Current unread badge count |
261
+ | `useChangelog()` | `featuredrop/react/hooks` | `{ features, newFeatures, newCount, dismiss, dismissAll, markAllSeen, getByCategory }` |
262
+ | `useTour(id)` | `featuredrop/react/hooks` | Imperative tour controls and step snapshot |
263
+ | `useTourSequencer(sequence)` | `featuredrop/react/hooks` | Ordered multi-tour orchestration |
264
+ | `useChecklist(id)` | `featuredrop/react/hooks` | Checklist progress + task controls |
265
+ | `useSurvey(id)` | `featuredrop/react/hooks` | Survey controls: `show`, `hide`, `askLater` |
266
+ | `useTabNotification()` | `featuredrop/react/hooks` | Browser tab title count: `"(3) My App"` |
267
+
268
+ > **When to use hooks vs components:** If your project uses shadcn/ui, Radix, or any custom design system, use hooks from `featuredrop/react/hooks`. If you want out-of-the-box UI, use components from `featuredrop/react`.
269
+
270
+ ---
271
+
272
+ ## AI-Native
273
+
274
+ FeatureDrop is built for the AI coding era. Your AI assistant already knows how to use it.
275
+
276
+ ### Claude Code Plugin
277
+
278
+ ```bash
279
+ # Install the plugin — Claude Code learns FeatureDrop's API automatically
280
+ /plugin install featuredrop
281
+ ```
282
+
283
+ Then just ask: *"Add a changelog widget with auto-expiring badges to my app"* — Claude handles the rest.
284
+
285
+ ### Cursor / Copilot
286
+
287
+ ```bash
288
+ # Auto-detect your IDE and copy the right context files
289
+ npx featuredrop ai-setup
290
+ ```
291
+
292
+ ### Tailwind Plugin
293
+
294
+ ```ts
295
+ // tailwind.config.ts
296
+ import { featureDropPlugin } from 'featuredrop/tailwind'
297
+
298
+ export default {
299
+ plugins: [featureDropPlugin()],
300
+ // Adds: fd-badge, fd-badge-dot, fd-badge-count, animations, CSS variables
301
+ // Auto dark mode, reduced-motion support
302
+ }
303
+ ```
238
304
 
239
305
  ---
240
306
 
package/context7.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://context7.com/schema/context7.json",
3
+ "projectTitle": "FeatureDrop",
4
+ "description": "Open-source product adoption toolkit — changelogs, badges, tours, checklists, hotspots, feedback widgets, surveys. Zero dependencies, < 3 kB core, 8 framework adapters, 12 storage adapters. Free Beamer/Pendo alternative.",
5
+ "folders": ["apps/docs/pages/docs", "skills", "README.md"],
6
+ "excludeFolders": ["node_modules", "dist", "coverage", ".git", "examples", "docs-local", ".claude"],
7
+ "excludeFiles": ["CHANGELOG.md", "CODE_OF_CONDUCT.md", "_app.tsx", "_meta.json"],
8
+ "rules": [
9
+ "Always use subpath imports: featuredrop/react, featuredrop/react/hooks, featuredrop/adapters",
10
+ "Prefer hooks from featuredrop/react/hooks for custom design systems (shadcn, Radix, etc.)",
11
+ "Features are defined in a JSON manifest with { id, label, description, releasedAt, showNewUntil? }",
12
+ "Core bundle is under 3 kB gzipped with zero production dependencies",
13
+ "All components support headless mode via render props for full customization"
14
+ ]
15
+ }
package/dist/index.d.cts CHANGED
@@ -232,6 +232,94 @@ interface AnalyticsCallbacks {
232
232
  /** Fired when all features are dismissed at once */
233
233
  onAllDismissed?: () => void;
234
234
  }
235
+ /** Display format hint from the engine */
236
+ type DisplayFormat = "badge" | "toast" | "modal" | "banner" | "inline" | "spotlight";
237
+ /** Interaction type tracked by the engine */
238
+ type InteractionType = "seen" | "dismissed" | "clicked" | "completed" | "snoozed" | "hovered" | "expanded";
239
+ /** Timing decision returned by the engine */
240
+ interface TimingDecision {
241
+ /** Whether to show the announcement now */
242
+ show: boolean;
243
+ /** Reason for the decision */
244
+ reason: string;
245
+ /** Suggested delay in ms if not showing now */
246
+ delayMs?: number;
247
+ /** Confidence level (0-1) */
248
+ confidence: number;
249
+ }
250
+ /** Format recommendation from the engine */
251
+ interface FormatRecommendation {
252
+ /** Recommended display format */
253
+ primary: DisplayFormat;
254
+ /** Fallback format if primary component isn't used */
255
+ fallback: DisplayFormat;
256
+ /** Reason for the recommendation */
257
+ reason: string;
258
+ }
259
+ /** Adoption score breakdown */
260
+ interface AdoptionScore {
261
+ /** Overall score (0-100) */
262
+ score: number;
263
+ /** Letter grade */
264
+ grade: "A" | "B" | "C" | "D" | "F";
265
+ /** Score breakdown */
266
+ breakdown: {
267
+ /** % of features the user has explored */
268
+ featuresExplored: number;
269
+ /** Rate of dismissals (lower is better) */
270
+ dismissRate: number;
271
+ /** Rate of tour/checklist completion */
272
+ completionRate: number;
273
+ /** Whether engagement is rising, stable, or declining */
274
+ engagementTrend: "rising" | "stable" | "declining";
275
+ };
276
+ /** Actionable recommendations */
277
+ recommendations: string[];
278
+ }
279
+ /** Per-feature adoption status */
280
+ interface FeatureAdoptionStatus {
281
+ featureId: string;
282
+ status: "unseen" | "seen" | "explored" | "adopted" | "dismissed";
283
+ firstSeen?: string;
284
+ lastInteraction?: string;
285
+ interactionCount: number;
286
+ }
287
+ /** Delivery context passed to the engine for timing decisions */
288
+ interface DeliveryContext {
289
+ /** Current route/path */
290
+ currentPath: string;
291
+ /** Seconds since session start */
292
+ sessionAge: number;
293
+ /** Dismissals in last 5 minutes */
294
+ recentDismissals: number;
295
+ /** Feature priority */
296
+ featurePriority: FeaturePriority;
297
+ }
298
+ /**
299
+ * Plugin interface for the FeatureDrop engine.
300
+ *
301
+ * The open-source library defines this interface.
302
+ * The proprietary @featuredrop/engine implements it.
303
+ * Users can also build their own engine implementation.
304
+ *
305
+ * The free library works perfectly without any engine.
306
+ */
307
+ interface FeatureDropEngine {
308
+ /** Decide whether to show a feature announcement now */
309
+ shouldShow(featureId: string, context: DeliveryContext): TimingDecision;
310
+ /** Recommend the best display format for a feature */
311
+ recommendFormat(featureId: string): FormatRecommendation;
312
+ /** Get the user's overall adoption score */
313
+ getAdoptionScore(): AdoptionScore;
314
+ /** Track a user interaction with a feature */
315
+ trackInteraction(featureId: string, type: InteractionType): void;
316
+ /** Get adoption status for a specific feature */
317
+ getFeatureAdoption(featureId: string): FeatureAdoptionStatus;
318
+ /** Initialize the engine (called by provider on mount) */
319
+ initialize?(): void;
320
+ /** Cleanup resources (called by provider on unmount) */
321
+ destroy?(): void;
322
+ }
235
323
 
236
324
  /**
237
325
  * Default audience matching logic.
@@ -337,4 +425,4 @@ declare class MemoryAdapter implements StorageAdapter {
337
425
  dismissAll(now: Date): Promise<void>;
338
426
  }
339
427
 
340
- export { type AnalyticsCallbacks, type AudienceMatchFn, type AudienceRule, type DismissalState, type FeatureCTA, type FeatureDependencies, type FeatureDependencyState, type FeatureDropAnimationPreset, type FeatureEntry, type FeatureFlagBridge, type FeatureManifest, type FeaturePriority, type FeatureTrigger, type FeatureType, type FeatureVariant, LocalStorageAdapter, type LocalStorageAdapterOptions, MemoryAdapter, type ServerStorageAdapter, type StorageAdapter, type TriggerContext, type UserContext, createManifest, getFeatureById, getNewFeatureCount, getNewFeatures, getNewFeaturesByCategory, getNewFeaturesSorted, hasNewFeature, isNew, matchesAudience };
428
+ export { type AdoptionScore, type AnalyticsCallbacks, type AudienceMatchFn, type AudienceRule, type DeliveryContext, type DismissalState, type DisplayFormat, type FeatureAdoptionStatus, type FeatureCTA, type FeatureDependencies, type FeatureDependencyState, type FeatureDropAnimationPreset, type FeatureDropEngine, type FeatureEntry, type FeatureFlagBridge, type FeatureManifest, type FeaturePriority, type FeatureTrigger, type FeatureType, type FeatureVariant, type FormatRecommendation, type InteractionType, LocalStorageAdapter, type LocalStorageAdapterOptions, MemoryAdapter, type ServerStorageAdapter, type StorageAdapter, type TimingDecision, type TriggerContext, type UserContext, createManifest, getFeatureById, getNewFeatureCount, getNewFeatures, getNewFeaturesByCategory, getNewFeaturesSorted, hasNewFeature, isNew, matchesAudience };
package/dist/index.d.ts CHANGED
@@ -232,6 +232,94 @@ interface AnalyticsCallbacks {
232
232
  /** Fired when all features are dismissed at once */
233
233
  onAllDismissed?: () => void;
234
234
  }
235
+ /** Display format hint from the engine */
236
+ type DisplayFormat = "badge" | "toast" | "modal" | "banner" | "inline" | "spotlight";
237
+ /** Interaction type tracked by the engine */
238
+ type InteractionType = "seen" | "dismissed" | "clicked" | "completed" | "snoozed" | "hovered" | "expanded";
239
+ /** Timing decision returned by the engine */
240
+ interface TimingDecision {
241
+ /** Whether to show the announcement now */
242
+ show: boolean;
243
+ /** Reason for the decision */
244
+ reason: string;
245
+ /** Suggested delay in ms if not showing now */
246
+ delayMs?: number;
247
+ /** Confidence level (0-1) */
248
+ confidence: number;
249
+ }
250
+ /** Format recommendation from the engine */
251
+ interface FormatRecommendation {
252
+ /** Recommended display format */
253
+ primary: DisplayFormat;
254
+ /** Fallback format if primary component isn't used */
255
+ fallback: DisplayFormat;
256
+ /** Reason for the recommendation */
257
+ reason: string;
258
+ }
259
+ /** Adoption score breakdown */
260
+ interface AdoptionScore {
261
+ /** Overall score (0-100) */
262
+ score: number;
263
+ /** Letter grade */
264
+ grade: "A" | "B" | "C" | "D" | "F";
265
+ /** Score breakdown */
266
+ breakdown: {
267
+ /** % of features the user has explored */
268
+ featuresExplored: number;
269
+ /** Rate of dismissals (lower is better) */
270
+ dismissRate: number;
271
+ /** Rate of tour/checklist completion */
272
+ completionRate: number;
273
+ /** Whether engagement is rising, stable, or declining */
274
+ engagementTrend: "rising" | "stable" | "declining";
275
+ };
276
+ /** Actionable recommendations */
277
+ recommendations: string[];
278
+ }
279
+ /** Per-feature adoption status */
280
+ interface FeatureAdoptionStatus {
281
+ featureId: string;
282
+ status: "unseen" | "seen" | "explored" | "adopted" | "dismissed";
283
+ firstSeen?: string;
284
+ lastInteraction?: string;
285
+ interactionCount: number;
286
+ }
287
+ /** Delivery context passed to the engine for timing decisions */
288
+ interface DeliveryContext {
289
+ /** Current route/path */
290
+ currentPath: string;
291
+ /** Seconds since session start */
292
+ sessionAge: number;
293
+ /** Dismissals in last 5 minutes */
294
+ recentDismissals: number;
295
+ /** Feature priority */
296
+ featurePriority: FeaturePriority;
297
+ }
298
+ /**
299
+ * Plugin interface for the FeatureDrop engine.
300
+ *
301
+ * The open-source library defines this interface.
302
+ * The proprietary @featuredrop/engine implements it.
303
+ * Users can also build their own engine implementation.
304
+ *
305
+ * The free library works perfectly without any engine.
306
+ */
307
+ interface FeatureDropEngine {
308
+ /** Decide whether to show a feature announcement now */
309
+ shouldShow(featureId: string, context: DeliveryContext): TimingDecision;
310
+ /** Recommend the best display format for a feature */
311
+ recommendFormat(featureId: string): FormatRecommendation;
312
+ /** Get the user's overall adoption score */
313
+ getAdoptionScore(): AdoptionScore;
314
+ /** Track a user interaction with a feature */
315
+ trackInteraction(featureId: string, type: InteractionType): void;
316
+ /** Get adoption status for a specific feature */
317
+ getFeatureAdoption(featureId: string): FeatureAdoptionStatus;
318
+ /** Initialize the engine (called by provider on mount) */
319
+ initialize?(): void;
320
+ /** Cleanup resources (called by provider on unmount) */
321
+ destroy?(): void;
322
+ }
235
323
 
236
324
  /**
237
325
  * Default audience matching logic.
@@ -337,4 +425,4 @@ declare class MemoryAdapter implements StorageAdapter {
337
425
  dismissAll(now: Date): Promise<void>;
338
426
  }
339
427
 
340
- export { type AnalyticsCallbacks, type AudienceMatchFn, type AudienceRule, type DismissalState, type FeatureCTA, type FeatureDependencies, type FeatureDependencyState, type FeatureDropAnimationPreset, type FeatureEntry, type FeatureFlagBridge, type FeatureManifest, type FeaturePriority, type FeatureTrigger, type FeatureType, type FeatureVariant, LocalStorageAdapter, type LocalStorageAdapterOptions, MemoryAdapter, type ServerStorageAdapter, type StorageAdapter, type TriggerContext, type UserContext, createManifest, getFeatureById, getNewFeatureCount, getNewFeatures, getNewFeaturesByCategory, getNewFeaturesSorted, hasNewFeature, isNew, matchesAudience };
428
+ export { type AdoptionScore, type AnalyticsCallbacks, type AudienceMatchFn, type AudienceRule, type DeliveryContext, type DismissalState, type DisplayFormat, type FeatureAdoptionStatus, type FeatureCTA, type FeatureDependencies, type FeatureDependencyState, type FeatureDropAnimationPreset, type FeatureDropEngine, type FeatureEntry, type FeatureFlagBridge, type FeatureManifest, type FeaturePriority, type FeatureTrigger, type FeatureType, type FeatureVariant, type FormatRecommendation, type InteractionType, LocalStorageAdapter, type LocalStorageAdapterOptions, MemoryAdapter, type ServerStorageAdapter, type StorageAdapter, type TimingDecision, type TriggerContext, type UserContext, createManifest, getFeatureById, getNewFeatureCount, getNewFeatures, getNewFeaturesByCategory, getNewFeaturesSorted, hasNewFeature, isNew, matchesAudience };
package/dist/preact.cjs CHANGED
@@ -1099,6 +1099,7 @@ function FeatureDropProvider({
1099
1099
  locale = "en",
1100
1100
  animation = "normal",
1101
1101
  translations: translationOverrides,
1102
+ engine,
1102
1103
  children
1103
1104
  }) {
1104
1105
  const analyticsRef = react.useRef(analytics);
@@ -1155,20 +1156,20 @@ function FeatureDropProvider({
1155
1156
  seenFeatureIds: readIdSet(SEEN_FEATURES_STORAGE_KEY),
1156
1157
  clickedFeatureIds: readIdSet(CLICKED_FEATURES_STORAGE_KEY),
1157
1158
  triggerContext: (() => {
1158
- const engine = triggerEngineRef.current;
1159
- if (!engine) return void 0;
1160
- engine.setElapsedMs(Date.now() - sessionStartedAtRef.current);
1161
- return engine.getContext();
1159
+ const engine2 = triggerEngineRef.current;
1160
+ if (!engine2) return void 0;
1161
+ engine2.setElapsedMs(Date.now() - sessionStartedAtRef.current);
1162
+ return engine2.getContext();
1162
1163
  })(),
1163
1164
  flagBridge
1164
1165
  })
1165
1166
  );
1166
1167
  const recompute = react.useCallback(() => {
1167
- const engine = triggerEngineRef.current;
1168
+ const engine2 = triggerEngineRef.current;
1168
1169
  let triggerContext;
1169
- if (engine) {
1170
- engine.setElapsedMs(Date.now() - sessionStartedAtRef.current);
1171
- triggerContext = engine.getContext();
1170
+ if (engine2) {
1171
+ engine2.setElapsedMs(Date.now() - sessionStartedAtRef.current);
1172
+ triggerContext = engine2.getContext();
1172
1173
  }
1173
1174
  setFeatureState(
1174
1175
  computeFeatureState({
@@ -1205,6 +1206,12 @@ function FeatureDropProvider({
1205
1206
  react.useEffect(() => {
1206
1207
  recompute();
1207
1208
  }, [recompute]);
1209
+ react.useEffect(() => {
1210
+ engine?.initialize?.();
1211
+ return () => {
1212
+ engine?.destroy?.();
1213
+ };
1214
+ }, [engine]);
1208
1215
  const hasTimeTriggers = react.useMemo(
1209
1216
  () => resolvedManifest.some((feature) => feature.trigger?.type === "time"),
1210
1217
  [resolvedManifest]
@@ -1487,7 +1494,8 @@ function FeatureDropProvider({
1487
1494
  trackUsageEvent,
1488
1495
  trackTriggerEvent,
1489
1496
  trackMilestone,
1490
- setTriggerPath
1497
+ setTriggerPath,
1498
+ engine: engine ?? null
1491
1499
  }),
1492
1500
  [
1493
1501
  resolvedManifest,
@@ -1521,7 +1529,8 @@ function FeatureDropProvider({
1521
1529
  trackUsageEvent,
1522
1530
  trackTriggerEvent,
1523
1531
  trackMilestone,
1524
- setTriggerPath
1532
+ setTriggerPath,
1533
+ engine
1525
1534
  ]
1526
1535
  );
1527
1536
  return /* @__PURE__ */ jsxRuntime.jsx(FeatureDropContext.Provider, { value, children });