featuredrop 1.1.0 → 1.3.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 +547 -4
- package/dist/admin.cjs +212 -0
- package/dist/admin.cjs.map +1 -0
- package/dist/admin.d.cts +176 -0
- package/dist/admin.d.ts +176 -0
- package/dist/admin.js +207 -0
- package/dist/admin.js.map +1 -0
- package/dist/angular.cjs +296 -0
- package/dist/angular.cjs.map +1 -0
- package/dist/angular.d.cts +233 -0
- package/dist/angular.d.ts +233 -0
- package/dist/angular.js +293 -0
- package/dist/angular.js.map +1 -0
- package/dist/bridges.cjs +401 -0
- package/dist/bridges.cjs.map +1 -0
- package/dist/bridges.d.cts +194 -0
- package/dist/bridges.d.ts +194 -0
- package/dist/bridges.js +394 -0
- package/dist/bridges.js.map +1 -0
- package/dist/ci.cjs +328 -0
- package/dist/ci.cjs.map +1 -0
- package/dist/ci.d.cts +176 -0
- package/dist/ci.d.ts +176 -0
- package/dist/ci.js +324 -0
- package/dist/ci.js.map +1 -0
- package/dist/featuredrop.cjs +1377 -0
- package/dist/featuredrop.cjs.map +1 -0
- package/dist/flags.cjs +51 -0
- package/dist/flags.cjs.map +1 -0
- package/dist/flags.d.cts +48 -0
- package/dist/flags.d.ts +48 -0
- package/dist/flags.js +47 -0
- package/dist/flags.js.map +1 -0
- package/dist/index.cjs +4734 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1516 -9
- package/dist/index.d.ts +1516 -9
- package/dist/index.js +4660 -71
- package/dist/index.js.map +1 -1
- package/dist/preact.cjs +7790 -0
- package/dist/preact.cjs.map +1 -0
- package/dist/preact.d.cts +1213 -0
- package/dist/preact.d.ts +1213 -0
- package/dist/preact.js +7760 -0
- package/dist/preact.js.map +1 -0
- package/dist/react.cjs +6678 -159
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +852 -112
- package/dist/react.d.ts +852 -112
- package/dist/react.js +6657 -156
- package/dist/react.js.map +1 -1
- package/dist/schema.cjs +292 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +345 -0
- package/dist/schema.d.ts +345 -0
- package/dist/schema.js +286 -0
- package/dist/schema.js.map +1 -0
- package/dist/solid.cjs +383 -0
- package/dist/solid.cjs.map +1 -0
- package/dist/solid.d.cts +246 -0
- package/dist/solid.d.ts +246 -0
- package/dist/solid.js +376 -0
- package/dist/solid.js.map +1 -0
- package/dist/svelte.cjs +339 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.js +334 -0
- package/dist/svelte.js.map +1 -0
- package/dist/testing.cjs +1543 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +361 -0
- package/dist/testing.d.ts +361 -0
- package/dist/testing.js +1536 -0
- package/dist/testing.js.map +1 -0
- package/dist/vue.cjs +1094 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.js +1082 -0
- package/dist/vue.js.map +1 -0
- package/dist/web-components.cjs +493 -0
- package/dist/web-components.cjs.map +1 -0
- package/dist/web-components.d.cts +215 -0
- package/dist/web-components.d.ts +215 -0
- package/dist/web-components.js +487 -0
- package/dist/web-components.js.map +1 -0
- package/package.json +184 -3
package/README.md
CHANGED
|
@@ -87,6 +87,14 @@ export const FEATURES = createManifest([
|
|
|
87
87
|
priority: 'critical',
|
|
88
88
|
cta: { label: 'Try it', url: '/journal' },
|
|
89
89
|
},
|
|
90
|
+
{
|
|
91
|
+
id: 'scheduled-reports',
|
|
92
|
+
label: 'Scheduled Reports',
|
|
93
|
+
releasedAt: '2026-02-23T00:00:00Z',
|
|
94
|
+
showNewUntil: '2026-03-23T00:00:00Z',
|
|
95
|
+
dependsOn: { clicked: ['ai-journal'] }, // progressive rollout
|
|
96
|
+
trigger: { type: 'page', match: '/reports/*' }, // contextual trigger
|
|
97
|
+
},
|
|
90
98
|
])
|
|
91
99
|
```
|
|
92
100
|
|
|
@@ -101,6 +109,10 @@ const storage = new LocalStorageAdapter({
|
|
|
101
109
|
})
|
|
102
110
|
```
|
|
103
111
|
|
|
112
|
+
For offline-heavy apps, use `IndexedDBAdapter` to persist the same state with IndexedDB + local fallback.
|
|
113
|
+
It also supports queued dismiss sync (`flushQueue`) + remote state reconciliation (`syncFromRemote`) for offline-first flows.
|
|
114
|
+
For remote/state sync, `RemoteAdapter` now includes retry + circuit-breaker protection and a monitoring-friendly `isHealthy()` check.
|
|
115
|
+
|
|
104
116
|
**3. Check what's new:**
|
|
105
117
|
|
|
106
118
|
```ts
|
|
@@ -112,6 +124,359 @@ hasNewFeature(FEATURES, '/journal', storage) // true/false
|
|
|
112
124
|
|
|
113
125
|
Works with **any framework**. Zero React dependency for vanilla use.
|
|
114
126
|
|
|
127
|
+
## Adoption Analytics
|
|
128
|
+
|
|
129
|
+
Pipe structured adoption events to PostHog/Amplitude/Mixpanel/Segment or your own endpoint.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { AnalyticsCollector, PostHogAdapter } from 'featuredrop'
|
|
133
|
+
|
|
134
|
+
const collector = new AnalyticsCollector({
|
|
135
|
+
adapter: new PostHogAdapter(posthog),
|
|
136
|
+
batchSize: 20,
|
|
137
|
+
flushInterval: 10_000,
|
|
138
|
+
sampleRate: 1,
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<FeatureDropProvider
|
|
144
|
+
manifest={FEATURES}
|
|
145
|
+
storage={storage}
|
|
146
|
+
collector={collector}
|
|
147
|
+
>
|
|
148
|
+
<App />
|
|
149
|
+
</FeatureDropProvider>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## A/B Announcement Variants
|
|
153
|
+
|
|
154
|
+
Run deterministic per-user announcement variants with weighted splits.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
{
|
|
158
|
+
id: 'ai-journal',
|
|
159
|
+
label: 'AI Decision Journal',
|
|
160
|
+
variants: {
|
|
161
|
+
control: { description: 'Track decisions with AI-powered insights.' },
|
|
162
|
+
treatment: { description: 'Never second-guess decisions again.' },
|
|
163
|
+
},
|
|
164
|
+
variantSplit: [50, 50],
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<FeatureDropProvider
|
|
170
|
+
manifest={FEATURES}
|
|
171
|
+
storage={storage}
|
|
172
|
+
variantKey={user.id} // stable key for deterministic assignment
|
|
173
|
+
>
|
|
174
|
+
<App />
|
|
175
|
+
</FeatureDropProvider>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Theme Engine
|
|
179
|
+
|
|
180
|
+
Theme all featuredrop components with CSS variables (no CSS-in-JS runtime).
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
import { ThemeProvider, ChangelogWidget } from 'featuredrop/react'
|
|
184
|
+
|
|
185
|
+
<ThemeProvider theme="dark">
|
|
186
|
+
<ChangelogWidget />
|
|
187
|
+
</ThemeProvider>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { createTheme } from 'featuredrop'
|
|
192
|
+
import { ThemeProvider, ChangelogPage } from 'featuredrop/react'
|
|
193
|
+
|
|
194
|
+
const myTheme = createTheme({
|
|
195
|
+
colors: { primary: '#7c3aed' },
|
|
196
|
+
radii: { lg: '16px' },
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
<ThemeProvider theme={myTheme}>
|
|
200
|
+
<ChangelogPage />
|
|
201
|
+
</ThemeProvider>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Presets: `light`, `dark`, `auto`, `minimal`, `vibrant`.
|
|
205
|
+
You can also pass `theme` directly to `ChangelogWidget` and `ChangelogPage` for component-scoped overrides.
|
|
206
|
+
|
|
207
|
+
## Internationalization
|
|
208
|
+
|
|
209
|
+
Use built-in locale packs or supply partial overrides:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
<FeatureDropProvider
|
|
213
|
+
manifest={FEATURES}
|
|
214
|
+
storage={storage}
|
|
215
|
+
locale="fr"
|
|
216
|
+
translations={{
|
|
217
|
+
submit: 'Envoyer maintenant',
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
<App />
|
|
221
|
+
</FeatureDropProvider>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Built-in locales: `en`, `es`, `fr`, `de`, `pt`, `zh-cn`, `ja`, `ko`, `ar`, `hi`.
|
|
225
|
+
Locale helpers include locale-aware date formatting, pluralized count copy, and RTL direction support for Arabic.
|
|
226
|
+
Use `formatRelativeTimeForLocale()` for localized "x days ago" labels, or set `dateFormat="relative"` on `ChangelogWidget` / `ChangelogPage`.
|
|
227
|
+
|
|
228
|
+
## Animation Presets
|
|
229
|
+
|
|
230
|
+
Choose built-in motion levels globally:
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
<FeatureDropProvider manifest={FEATURES} storage={storage} animation="subtle">
|
|
234
|
+
<App />
|
|
235
|
+
</FeatureDropProvider>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Available presets: `none`, `subtle`, `normal`, `playful`.
|
|
239
|
+
Reduced-motion users automatically receive `none`.
|
|
240
|
+
Built-in `ChangelogWidget`, `AnnouncementModal`, and `Toast` include enter/exit animations, and `Tour`, `Survey`, `FeedbackWidget`, `Spotlight`, `SpotlightChain`, and `Hotspot` follow the same preset for enter/pulse motion.
|
|
241
|
+
|
|
242
|
+
## Custom Renderer Protocol
|
|
243
|
+
|
|
244
|
+
Need full UI control? Use a UI-agnostic renderer with state/actions/computed helpers:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { createChangelogRenderer } from 'featuredrop'
|
|
248
|
+
|
|
249
|
+
const renderer = createChangelogRenderer({ manifest: FEATURES, storage })
|
|
250
|
+
renderer.subscribe((state) => console.log(state.newCount))
|
|
251
|
+
renderer.actions.dismiss('ai-journal')
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Accessibility
|
|
255
|
+
|
|
256
|
+
featuredrop components include keyboard navigation, focus trapping/return for dialogs, ARIA live regions, and reduced-motion support.
|
|
257
|
+
|
|
258
|
+
Automated accessibility regression checks run in CI via `axe-core` + `vitest-axe` (`src/__tests__/accessibility-axe.test.tsx`).
|
|
259
|
+
|
|
260
|
+
## Changelog-as-Code
|
|
261
|
+
|
|
262
|
+
Manage announcements as markdown files in your repo and compile to a manifest:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
npx featuredrop init --format markdown
|
|
266
|
+
npx featuredrop add --label "AI Journal" --category ai --description "Track decisions with AI."
|
|
267
|
+
npx featuredrop build --pattern "features/**/*.md" --out featuredrop.manifest.json
|
|
268
|
+
npx featuredrop validate --pattern "features/**/*.md"
|
|
269
|
+
npx featuredrop stats --pattern "features/**/*.md"
|
|
270
|
+
npx featuredrop doctor --pattern "features/**/*.md"
|
|
271
|
+
npx featuredrop generate-rss --pattern "features/**/*.md" --out featuredrop.rss.xml
|
|
272
|
+
npx featuredrop generate-changelog --pattern "features/**/*.md" --out CHANGELOG.generated.md
|
|
273
|
+
npx featuredrop migrate --from beamer --input beamer-export.json --out featuredrop.manifest.json
|
|
274
|
+
npx featuredrop migrate --from headway --input headway-export.json --out featuredrop.manifest.json
|
|
275
|
+
npx featuredrop migrate --from announcekit --input announcekit-export.json --out featuredrop.manifest.json
|
|
276
|
+
npx featuredrop migrate --from canny --input canny-export.json --out featuredrop.manifest.json
|
|
277
|
+
npx featuredrop migrate --from launchnotes --input launchnotes-export.json --out featuredrop.manifest.json
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Example feature file:
|
|
281
|
+
|
|
282
|
+
```md
|
|
283
|
+
---
|
|
284
|
+
id: ai-journal
|
|
285
|
+
label: AI Journal
|
|
286
|
+
type: feature
|
|
287
|
+
category: ai
|
|
288
|
+
releasedAt: 2026-02-15T00:00:00Z
|
|
289
|
+
showNewUntil: 2026-03-15T00:00:00Z
|
|
290
|
+
cta:
|
|
291
|
+
label: Try it now
|
|
292
|
+
url: /journal
|
|
293
|
+
---
|
|
294
|
+
Track decisions and outcomes with AI-powered insights.
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## CMS Adapters
|
|
298
|
+
|
|
299
|
+
Pull releases from your CMS and map them into `FeatureEntry[]`:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import { ContentfulAdapter, SanityAdapter, StrapiAdapter, NotionAdapter, MarkdownAdapter } from 'featuredrop'
|
|
303
|
+
|
|
304
|
+
const contentful = new ContentfulAdapter({
|
|
305
|
+
spaceId: process.env.CONTENTFUL_SPACE!,
|
|
306
|
+
accessToken: process.env.CONTENTFUL_TOKEN!,
|
|
307
|
+
contentType: 'featureRelease',
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
const entries = await contentful.load()
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Each adapter accepts optional `fieldMapping` overrides so you can map your content model fields without reshaping server responses.
|
|
314
|
+
|
|
315
|
+
## Notification Bridges
|
|
316
|
+
|
|
317
|
+
Use framework-agnostic bridge helpers to fan out release notifications:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
import {
|
|
321
|
+
SlackBridge,
|
|
322
|
+
DiscordBridge,
|
|
323
|
+
EmailDigestGenerator,
|
|
324
|
+
WebhookBridge,
|
|
325
|
+
RSSFeedGenerator,
|
|
326
|
+
} from 'featuredrop/bridges'
|
|
327
|
+
|
|
328
|
+
await SlackBridge.notify(feature, { webhookUrl: process.env.SLACK_WEBHOOK! })
|
|
329
|
+
await DiscordBridge.notify(feature, { webhookUrl: process.env.DISCORD_WEBHOOK! })
|
|
330
|
+
await WebhookBridge.post(feature, { url: 'https://api.example.com/hooks/features' })
|
|
331
|
+
|
|
332
|
+
const html = EmailDigestGenerator.generate(features, { title: 'Weekly updates' })
|
|
333
|
+
const rss = RSSFeedGenerator.generate(features, { title: 'Product Updates' })
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## CI Utilities
|
|
337
|
+
|
|
338
|
+
Use `featuredrop/ci` for manifest diffing in pull-request checks:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import { diffManifest, generateChangelogDiff, validateManifestForCI } from 'featuredrop/ci'
|
|
342
|
+
|
|
343
|
+
const diff = diffManifest(beforeManifest, afterManifest)
|
|
344
|
+
const summary = generateChangelogDiff(diff, { includeFieldChanges: true })
|
|
345
|
+
const validation = validateManifestForCI(afterManifest)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Run bundle budget checks after `pnpm build`:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
pnpm size-check
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Feature Flag Bridges
|
|
355
|
+
|
|
356
|
+
Gate announcement visibility behind flags via `feature.flagKey`:
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
import { createFlagBridge, LaunchDarklyBridge, PostHogBridge } from 'featuredrop/flags'
|
|
360
|
+
|
|
361
|
+
const bridge = createFlagBridge({
|
|
362
|
+
isEnabled: (flagKey) => myFlagService.isOn(flagKey),
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
// Provider + feature entry
|
|
366
|
+
<FeatureDropProvider manifest={FEATURES} storage={storage} flagBridge={bridge} />
|
|
367
|
+
// { id: 'ai-journal', flagKey: 'ai-journal-enabled', ... }
|
|
368
|
+
|
|
369
|
+
// Or vendor bridges
|
|
370
|
+
const ld = new LaunchDarklyBridge(ldClient)
|
|
371
|
+
const ph = new PostHogBridge(posthog)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Multi-Product Support
|
|
375
|
+
|
|
376
|
+
Scope visibility by product using `FeatureDropProvider` + `feature.product`:
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
<FeatureDropProvider manifest={FEATURES} storage={storage} product="askverdict" />
|
|
380
|
+
// feature.product can be "askverdict", "other-product", or "*" for shared entries
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Admin Components
|
|
384
|
+
|
|
385
|
+
Use optional admin UI primitives via `featuredrop/admin`:
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
import {
|
|
389
|
+
ManifestEditor,
|
|
390
|
+
ScheduleCalendar,
|
|
391
|
+
PreviewPanel,
|
|
392
|
+
AudienceBuilder,
|
|
393
|
+
} from 'featuredrop/admin'
|
|
394
|
+
|
|
395
|
+
<ManifestEditor features={features} onSave={saveManifest} />
|
|
396
|
+
<ScheduleCalendar features={features} onSchedule={scheduleFeature} />
|
|
397
|
+
<PreviewPanel feature={selectedFeature} components={['badge', 'changelog', 'toast']} />
|
|
398
|
+
<AudienceBuilder segments={['free', 'pro']} roles={['admin', 'viewer']} onSave={saveAudience} />
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Resilience
|
|
402
|
+
|
|
403
|
+
Built-in React exports are wrapped in internal error boundaries. If a featuredrop component throws, it unmounts itself (no host app crash) and optionally reports through provider `onError`.
|
|
404
|
+
|
|
405
|
+
## Schema Validation
|
|
406
|
+
|
|
407
|
+
Validate manifest JSON in CI or tooling pipelines.
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
import {
|
|
411
|
+
featureEntrySchema,
|
|
412
|
+
featureEntryJsonSchema,
|
|
413
|
+
validateManifest,
|
|
414
|
+
} from 'featuredrop/schema'
|
|
415
|
+
|
|
416
|
+
featureEntrySchema.parse({
|
|
417
|
+
id: 'ai-journal',
|
|
418
|
+
label: 'AI Journal',
|
|
419
|
+
releasedAt: '2026-02-15T00:00:00Z',
|
|
420
|
+
showNewUntil: '2026-03-15T00:00:00Z',
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
const result = validateManifest(data)
|
|
424
|
+
if (!result.valid) {
|
|
425
|
+
throw new Error(result.errors.map((e) => `${e.path}: ${e.message}`).join("; "))
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
console.log(featureEntryJsonSchema.properties.id.type) // "string"
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Testing Utilities
|
|
432
|
+
|
|
433
|
+
Use `featuredrop/testing` to speed up unit and component tests.
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
import { render, screen } from '@testing-library/react'
|
|
437
|
+
import { useNewCount } from 'featuredrop/react'
|
|
438
|
+
import { createMockManifest, createMockStorage, createTestProvider } from 'featuredrop/testing'
|
|
439
|
+
|
|
440
|
+
const manifest = createMockManifest([{ label: 'AI Journal', releasedAt: 'today', showNewUntil: '+14d' }])
|
|
441
|
+
const storage = createMockStorage()
|
|
442
|
+
const Wrapper = createTestProvider({ manifest, storage })
|
|
443
|
+
|
|
444
|
+
function Count() {
|
|
445
|
+
return <span>{useNewCount()}</span>
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
render(<Count />, { wrapper: Wrapper })
|
|
449
|
+
expect(screen.getByText('1')).toBeInTheDocument()
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Playground & Online Demos
|
|
453
|
+
|
|
454
|
+
Use the lightweight component playground for quick UI iteration:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
pnpm --dir examples/sandbox-react install
|
|
458
|
+
pnpm playground
|
|
459
|
+
pnpm playground:build
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
One-click editable demos:
|
|
463
|
+
|
|
464
|
+
- React sandbox source: `examples/sandbox-react`
|
|
465
|
+
- StackBlitz: https://stackblitz.com/github/GLINCKER/featuredrop/tree/main/examples/sandbox-react
|
|
466
|
+
- CodeSandbox: https://codesandbox.io/p/sandbox/github/GLINCKER/featuredrop/tree/main/examples/sandbox-react
|
|
467
|
+
|
|
468
|
+
## Docs Site (Nextra + Vercel)
|
|
469
|
+
|
|
470
|
+
The repo now includes a docs app scaffold at `apps/docs` for a fast validation launch on Vercel.
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
pnpm docs:install
|
|
474
|
+
pnpm docs:dev
|
|
475
|
+
pnpm docs:build
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
The docs app is static-export ready, so it can also deploy on Cloudflare Pages or GitHub Pages.
|
|
479
|
+
|
|
115
480
|
## Components
|
|
116
481
|
|
|
117
482
|
Everything you'd expect from Beamer or Headway — but free, self-hosted, and headless-first.
|
|
@@ -129,6 +494,9 @@ import { ChangelogWidget } from 'featuredrop/react'
|
|
|
129
494
|
// Or modal / popover variant
|
|
130
495
|
<ChangelogWidget variant="modal" title="Release Notes" />
|
|
131
496
|
|
|
497
|
+
// Enable emoji reactions on entries
|
|
498
|
+
<ChangelogWidget showReactions />
|
|
499
|
+
|
|
132
500
|
// Fully headless
|
|
133
501
|
<ChangelogWidget>
|
|
134
502
|
{({ isOpen, toggle, features, count, dismissAll }) => (
|
|
@@ -177,6 +545,124 @@ import { Toast } from 'featuredrop/react'
|
|
|
177
545
|
<Toast featureIds={["ai-journal"]} autoDismissMs={5000} />
|
|
178
546
|
```
|
|
179
547
|
|
|
548
|
+
### Product Tours
|
|
549
|
+
|
|
550
|
+
Guided, multi-step onboarding with keyboard navigation and persistence.
|
|
551
|
+
|
|
552
|
+
```tsx
|
|
553
|
+
import { Tour, useTour } from 'featuredrop/react'
|
|
554
|
+
|
|
555
|
+
<Tour id="onboarding" steps={steps} />
|
|
556
|
+
const { startTour, nextStep, skipTour } = useTour('onboarding')
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Onboarding Checklist
|
|
560
|
+
|
|
561
|
+
Task-based onboarding that can trigger tours, links, or callbacks.
|
|
562
|
+
|
|
563
|
+
```tsx
|
|
564
|
+
import { Checklist, useChecklist } from 'featuredrop/react'
|
|
565
|
+
|
|
566
|
+
<Checklist id="getting-started" tasks={tasks} />
|
|
567
|
+
const { completeTask, progress } = useChecklist('getting-started')
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Feedback Widget
|
|
571
|
+
|
|
572
|
+
Collect lightweight in-app feedback with optional emoji and screenshots.
|
|
573
|
+
|
|
574
|
+
```tsx
|
|
575
|
+
import { FeedbackWidget } from 'featuredrop/react'
|
|
576
|
+
|
|
577
|
+
<FeedbackWidget
|
|
578
|
+
featureId="ai-journal"
|
|
579
|
+
onSubmit={async (payload) => {
|
|
580
|
+
await fetch('/api/feedback', { method: 'POST', body: JSON.stringify(payload) })
|
|
581
|
+
}}
|
|
582
|
+
showEmoji
|
|
583
|
+
showScreenshot
|
|
584
|
+
rateLimit="1-per-feature"
|
|
585
|
+
/>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### Survey (NPS / CSAT / CES / Custom)
|
|
589
|
+
|
|
590
|
+
Run micro-surveys with trigger rules and imperative controls.
|
|
591
|
+
|
|
592
|
+
```tsx
|
|
593
|
+
import { Survey, useSurvey } from 'featuredrop/react'
|
|
594
|
+
|
|
595
|
+
<Survey
|
|
596
|
+
id="nps-main"
|
|
597
|
+
type="nps"
|
|
598
|
+
prompt="How likely are you to recommend us?"
|
|
599
|
+
triggerRules={{ minDaysSinceSignup: 7, signupAt: user.createdAt }}
|
|
600
|
+
onSubmit={saveSurvey}
|
|
601
|
+
/>
|
|
602
|
+
|
|
603
|
+
const { show } = useSurvey('nps-main')
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Feature Request Voting
|
|
607
|
+
|
|
608
|
+
Capture and rank product requests with local persistence and optional webhook sync.
|
|
609
|
+
|
|
610
|
+
```tsx
|
|
611
|
+
import { FeatureRequestButton, FeatureRequestForm } from 'featuredrop/react'
|
|
612
|
+
|
|
613
|
+
<FeatureRequestButton featureId="dark-mode" requestTitle="Dark mode" />
|
|
614
|
+
|
|
615
|
+
<FeatureRequestForm
|
|
616
|
+
categories={['UI', 'Performance', 'Integration', 'Other']}
|
|
617
|
+
onSubmit={async (request) => {
|
|
618
|
+
await fetch('/api/requests', { method: 'POST', body: JSON.stringify(request) })
|
|
619
|
+
}}
|
|
620
|
+
/>
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Hotspots & Tooltips
|
|
624
|
+
|
|
625
|
+
Persistent contextual hints attached to specific UI targets.
|
|
626
|
+
|
|
627
|
+
```tsx
|
|
628
|
+
import { Hotspot, TooltipGroup } from 'featuredrop/react'
|
|
629
|
+
|
|
630
|
+
<TooltipGroup maxVisible={1}>
|
|
631
|
+
<Hotspot id="export-help" target="#export-btn" frequency="once">
|
|
632
|
+
Export supports CSV, PDF, and Excel.
|
|
633
|
+
</Hotspot>
|
|
634
|
+
</TooltipGroup>
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Announcement Modal
|
|
638
|
+
|
|
639
|
+
Priority-based modal announcements with optional slide carousel.
|
|
640
|
+
|
|
641
|
+
```tsx
|
|
642
|
+
import { AnnouncementModal } from 'featuredrop/react'
|
|
643
|
+
|
|
644
|
+
<AnnouncementModal
|
|
645
|
+
feature={criticalFeature}
|
|
646
|
+
trigger="auto"
|
|
647
|
+
frequency="once"
|
|
648
|
+
/>
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Spotlight Chain
|
|
652
|
+
|
|
653
|
+
Lightweight chained spotlights for "here are 3 new things" flows.
|
|
654
|
+
|
|
655
|
+
```tsx
|
|
656
|
+
import { SpotlightChain } from 'featuredrop/react'
|
|
657
|
+
|
|
658
|
+
<SpotlightChain
|
|
659
|
+
steps={[
|
|
660
|
+
{ target: '#sidebar', content: 'New navigation' },
|
|
661
|
+
{ target: '#search', content: 'Global search' },
|
|
662
|
+
]}
|
|
663
|
+
/>
|
|
664
|
+
```
|
|
665
|
+
|
|
180
666
|
### NewBadge
|
|
181
667
|
|
|
182
668
|
Headless badge component with variants. Zero CSS framework dependency.
|
|
@@ -211,7 +697,27 @@ npm install featuredrop react # react is an optional peer dep
|
|
|
211
697
|
```tsx
|
|
212
698
|
import { FeatureDropProvider } from 'featuredrop/react'
|
|
213
699
|
|
|
214
|
-
<FeatureDropProvider manifest={FEATURES} storage={storage}>
|
|
700
|
+
<FeatureDropProvider manifest={FEATURES} storage={storage} appVersion="2.5.1">
|
|
701
|
+
<App />
|
|
702
|
+
</FeatureDropProvider>
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
**Throttling + quiet mode:**
|
|
706
|
+
|
|
707
|
+
```tsx
|
|
708
|
+
<FeatureDropProvider
|
|
709
|
+
manifest={FEATURES}
|
|
710
|
+
storage={storage}
|
|
711
|
+
throttle={{
|
|
712
|
+
maxSimultaneousBadges: 3,
|
|
713
|
+
maxSimultaneousSpotlights: 1,
|
|
714
|
+
maxToastsPerSession: 3,
|
|
715
|
+
minTimeBetweenModals: 30000,
|
|
716
|
+
minTimeBetweenTours: 86400000,
|
|
717
|
+
sessionCooldown: 5000,
|
|
718
|
+
respectDoNotDisturb: true,
|
|
719
|
+
}}
|
|
720
|
+
>
|
|
215
721
|
<App />
|
|
216
722
|
</FeatureDropProvider>
|
|
217
723
|
```
|
|
@@ -248,11 +754,28 @@ import { ChangelogWidget } from 'featuredrop/react'
|
|
|
248
754
|
| `useNewFeature(key)` | Single nav item: `{ isNew, feature, dismiss }` |
|
|
249
755
|
| `useNewCount()` | Just the badge count |
|
|
250
756
|
| `useTabNotification()` | Updates browser tab title with count |
|
|
757
|
+
| `useTour(id)` | Imperative tour controls and current step snapshot |
|
|
758
|
+
| `useTourSequencer(sequence)` | Ordered multi-tour orchestration by feature readiness |
|
|
759
|
+
| `useChecklist(id)` | Imperative checklist controls + progress |
|
|
760
|
+
| `useSurvey(id)` | Imperative survey controls (`show`, `hide`, `askLater`) + state |
|
|
251
761
|
| `<NewBadge />` | Headless badge: `pill`, `dot`, or `count` variant |
|
|
252
|
-
| `<ChangelogWidget />` | Full changelog feed with trigger button |
|
|
762
|
+
| `<ChangelogWidget />` | Full changelog feed with trigger button + optional reactions |
|
|
763
|
+
| `<ChangelogPage />` | Full-page changelog with filters/search/pagination |
|
|
253
764
|
| `<Spotlight />` | Pulsing beacon attached to DOM elements |
|
|
254
765
|
| `<Banner />` | Announcement banner with variants |
|
|
255
766
|
| `<Toast />` | Stackable toast notifications |
|
|
767
|
+
| `<Tour />` | Multi-step guided product tour |
|
|
768
|
+
| `<Checklist />` | Onboarding task checklist |
|
|
769
|
+
| `<FeedbackWidget />` | In-app feedback form with category/emoji/screenshot support |
|
|
770
|
+
| `<Survey />` | NPS/CSAT/CES/custom survey engine with trigger rules |
|
|
771
|
+
| `<FeatureRequestButton />` | Per-feature voting button with persisted vote guard |
|
|
772
|
+
| `<FeatureRequestForm />` | Request capture form + sortable request list |
|
|
773
|
+
| `<Hotspot />` / `<TooltipGroup />` | Contextual tooltips with visibility caps |
|
|
774
|
+
| `<AnnouncementModal />` | Priority/frequency-gated modal announcements |
|
|
775
|
+
| `<SpotlightChain />` | Lightweight chained spotlight walkthrough |
|
|
776
|
+
|
|
777
|
+
`useFeatureDrop()` also exposes queue/throttle controls: `queuedFeatures`, `totalNewCount`, `quietMode`, `setQuietMode`, `markFeatureSeen`, `markFeatureClicked`, toast-slot helpers, modal/tour pacing checks, and spotlight slot controls.
|
|
778
|
+
It also exposes trigger runtime helpers: `trackUsageEvent`, `trackTriggerEvent`, `trackMilestone`, and `setTriggerPath`.
|
|
256
779
|
|
|
257
780
|
**Analytics integration:**
|
|
258
781
|
|
|
@@ -260,6 +783,7 @@ import { ChangelogWidget } from 'featuredrop/react'
|
|
|
260
783
|
<FeatureDropProvider
|
|
261
784
|
manifest={FEATURES}
|
|
262
785
|
storage={storage}
|
|
786
|
+
appVersion="2.5.1" // optional semver for version-pinned features
|
|
263
787
|
analytics={{
|
|
264
788
|
onFeatureSeen: (f) => posthog.capture('feature_seen', { id: f.id }),
|
|
265
789
|
onFeatureDismissed: (f) => posthog.capture('feature_dismissed', { id: f.id }),
|
|
@@ -311,7 +835,12 @@ Read the full [Architecture doc](docs/ARCHITECTURE.md) for cross-device sync flo
|
|
|
311
835
|
| **Price** | **Free** | $59-399/mo | $49-249/mo | $79-299/mo | $79+ |
|
|
312
836
|
| Auto-expiring badges | Yes | - | - | - | - |
|
|
313
837
|
| Changelog widget | Yes | Yes | Yes | Yes | Yes |
|
|
838
|
+
| Product tours | Yes | - | - | - | - |
|
|
839
|
+
| Onboarding checklists | Yes | - | - | - | - |
|
|
314
840
|
| Spotlight/beacon | Yes | - | - | - | - |
|
|
841
|
+
| Hotspot tooltips | Yes | - | - | - | - |
|
|
842
|
+
| Announcement modal | Yes | - | - | - | - |
|
|
843
|
+
| Spotlight chaining | Yes | - | - | - | - |
|
|
315
844
|
| Toast notifications | Yes | - | - | - | - |
|
|
316
845
|
| Announcement banner | Yes | - | - | - | - |
|
|
317
846
|
| Tab title notification | Yes | - | - | - | - |
|
|
@@ -334,20 +863,34 @@ Read the full [Architecture doc](docs/ARCHITECTURE.md) for cross-device sync flo
|
|
|
334
863
|
|-----------|--------|--------|
|
|
335
864
|
| React / Next.js | Stable | `featuredrop/react` |
|
|
336
865
|
| Vanilla JS | Stable | `featuredrop` |
|
|
337
|
-
|
|
|
338
|
-
|
|
|
866
|
+
| SolidJS | Preview | `featuredrop/solid` |
|
|
867
|
+
| Preact (compat) | Preview | `featuredrop/preact` |
|
|
868
|
+
| Web Components | Preview | `featuredrop/web-components` |
|
|
869
|
+
| Angular | Preview | `featuredrop/angular` |
|
|
870
|
+
| Vue 3 | Preview | `featuredrop/vue` |
|
|
871
|
+
| Svelte 5 | Preview (store bindings) | `featuredrop/svelte` |
|
|
339
872
|
|
|
340
873
|
## Documentation
|
|
341
874
|
|
|
875
|
+
- [Docs App Source](apps/docs/) — Next.js + Nextra site scaffold (`/`, `/docs`, `/playground`)
|
|
876
|
+
- [Docs Site Guide](docs/DOCS_SITE.md) — Local commands and Vercel deployment setup
|
|
342
877
|
- [API Reference](docs/API.md) — All functions, adapters, hooks, components
|
|
878
|
+
- [Recipes](docs/RECIPES.md) — Copy-paste integration patterns (sidebar badge, tours, migration, A/B tests)
|
|
343
879
|
- [Architecture](docs/ARCHITECTURE.md) — Three-check algorithm, cross-device sync, custom adapters
|
|
344
880
|
- [Next.js Example](examples/nextjs/) — Full App Router integration
|
|
345
881
|
- [Vanilla Example](examples/vanilla/) — Plain HTML, zero build step
|
|
882
|
+
- [React Sandbox](examples/sandbox-react/) — Interactive local/online playground
|
|
346
883
|
|
|
347
884
|
## Contributing
|
|
348
885
|
|
|
349
886
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, commit conventions, and how releases work.
|
|
350
887
|
|
|
888
|
+
## Security
|
|
889
|
+
|
|
890
|
+
- Report vulnerabilities privately via [SECURITY.md](SECURITY.md).
|
|
891
|
+
- CI includes CodeQL static analysis on pull requests and `main`.
|
|
892
|
+
- `pnpm security-check` scans runtime source for unsafe execution/rendering patterns.
|
|
893
|
+
|
|
351
894
|
## License
|
|
352
895
|
|
|
353
896
|
MIT © [Glincker](https://glincker.com)
|