We welcome contributions! Whether it's a bug fix, new adapter, framework binding, or documentation improvement.
# Fork and clone
git clone https://github.com/YOUR_USERNAME/featuredrop.git
cd featuredrop
# Install dependencies
pnpm install
# Run the full check suite
pnpm test # 121 tests
pnpm typecheck # TypeScript strict mode
pnpm build # Dual ESM/CJS outputsrc/
├── core.ts # isNew, getNewFeatures, getNewFeaturesSorted
├── types.ts # FeatureEntry, StorageAdapter, AnalyticsCallbacks
├── helpers.ts # createManifest, getFeatureById, etc.
├── adapters/
│ ├── local-storage.ts # Browser localStorage + watermark
│ └── memory.ts # In-memory (testing/SSR)
├── react/
│ ├── provider.tsx # FeatureDropProvider (React context + analytics)
│ ├── context.ts # Context type with sorted features
│ ├── hooks/
│ │ ├── use-feature-drop.ts # Full context hook
│ │ ├── use-new-feature.ts # Single nav item hook
│ │ ├── use-new-count.ts # Badge count hook
│ │ └── use-tab-notification.ts # Browser tab title hook
│ └── components/
│ ├── new-badge.tsx # Headless badge (pill/dot/count)
│ ├── changelog-widget.tsx # Changelog feed (panel/modal/popover)
│ ├── spotlight.tsx # Pulsing beacon + tooltip
│ ├── banner.tsx # Announcement banner
│ └── toast.tsx # Stackable toast notifications
└── __tests__/ # Vitest test files (6 files, 121 tests)
Two entry points:
src/index.ts→featuredrop(core, zero deps)src/react/index.ts→featuredrop/react(React bindings, hooks, components)
- Branch from
main:git checkout -b feat/my-feature - Write code — TypeScript strict, no
anytypes - Add tests — every new feature needs tests
- Run checks:
pnpm test && pnpm typecheck && pnpm build - Commit using Conventional Commits
- Open PR against
main
<type>(<scope>): <description>
| Type | Release Effect | Example |
|---|---|---|
feat |
Minor (1.x.0) | feat: add Vue composables |
fix |
Patch (1.0.x) | fix: handle null watermark in SSR |
feat! |
Major (x.0.0) | feat!: rename StorageAdapter methods |
perf |
Patch | perf: memoize getNewFeatures result |
refactor |
Patch | refactor: extract adapter base class |
docs |
None | docs: add Vue integration example |
test |
None | test: add SSR fallback tests |
chore |
None | chore: update dev dependencies |
Scopes (optional): core, react, adapters, ci, deps
Commitlint enforces this format on PRs via CI.
Releases are fully automated via GitHub Actions:
- PR merged to
mainwithfeat:orfix:commits - CI scans all commits since last tag for the highest-priority release type
- Version bumped, npm published with OIDC provenance, GitHub Release created
- Manual release: Actions → Auto Release → Run workflow
No manual version bumping needed. Just write good commit messages.
Adapters implement the StorageAdapter interface:
import type { StorageAdapter } from 'featuredrop'
export class MyAdapter implements StorageAdapter {
getWatermark(): string | null { /* ... */ }
getDismissedIds(): ReadonlySet<string> { /* ... */ }
dismiss(id: string): void { /* ... */ }
async dismissAll(now: Date): Promise<void> { /* ... */ }
}Key rules:
getWatermark()andgetDismissedIds()must be synchronousdismissAll()is the only async method (for server writes)- Handle missing
window/localStoragefor SSR - Return
new Set()(notnull) fromgetDismissedIds()when empty
Follow the React pattern in src/react/:
- Create
src/<framework>/directory - Add entry point in
tsup.config.ts - Add subpath export in
package.json - Framework should be an optional
peerDependency - Add
"use client"banner for SSR frameworks (handled in tsup config) - Write tests with the framework's testing library
- Core logic: Pure functions, test with
makeFeature()andmakeStorage()helpers - Adapters: Mock
localStoragewithvi.stubGlobal, test SSR by deletingwindow - React: Use
@testing-library/react, wrap components in testFeatureDropProvider
pnpm test # Run all tests
pnpm test:watch # Watch mode
pnpm test:coverage # With coverage report- TypeScript strict mode — no
any, noascasts without justification - Zero CSS framework coupling in components — use CSS custom properties
vitestfor all tests- Keep imports from
featuredrop(not relative paths) in framework bindings
- Use the bug report or feature request templates
- Include reproduction steps and your environment (Node version, framework, browser)
- For security issues, email hello@glincker.com directly