react-incremental-funnel Knowledge Base
Bundle OKF 0.2 · 10 conceitos · olivercox/react-incremental-funnel
Open source Repository Open in the app JSON README (API)
About
# react-incremental-funnel Knowledge Base
Local, source-grounded project memory for maintainers and coding agents working
on the `react-incremental-funnel` TypeScript React library. Use it to understand
the project quickly, keep public API knowledge synchronized, preserve important
decisions, and follow the repository's documentation and release loop.
## Start Here
* [Project overview](project-overview.md) - purpose, architecture, examples,
validation commands, and release path.
* [Public API and contracts](public-api.md) - source-linked map of exported
funnel, persistence, validation, lifecycle, and storage interfaces.
* [Agent rules](AGENTS.md) - when agents must read or update this wiki and the
privacy and publication boundaries they must preserve.
## Decisions
* [Decisions](decisions/) - durable technical choices and their tradeoffs.
* [Persistence and privacy boundaries](decisions/persistence-and-privacy.md) -
why storage is field-scoped and sensitive values require stricter
Details
- Kind
- OKF bundles
- Topic
- AI, RAG & memory
- Publisher
- olivercox
- Origin
- okf_github
- Category
- dados
- Version
- 0.2
- Open pull requests
- 6
- Last push
- 2026-09-02T11:14:35Z
- Repository state
- ativo
- Language
- TypeScript
- License
- MIT
- Added
- 2026-09-09 05:11:22
- Updated
- 2026-09-09 05:11:22
- Origin id
olivercox/react-incremental-funnel:Wiki/index.md
README
# react-incremental-funnel
TypeScript-first React package for building incremental funnel flows with a small runtime API and exported types.
## Installation
```bash
npm install react-incremental-funnel
```
## Vite example applications
This repository includes runnable Vite + React examples:
```text
examples/
basic-vite/
api-backed-vite/
```
These examples use generic mock data only (no Good Life Sorted endpoints, schemas, field names, credentials, or business logic).
### Run `basic-vite`
```bash
cd examples/basic-vite
npm install
npm run dev
```
`basic-vite` demonstrates:
- `useIncrementalFunnel` initialization
- step navigation and step completion/incompletion
- field persistence policies (`local`, `session`, `memory`)
- per-field TTL expiry
- resume/start-again UX
- submit lifecycle with a mock client-side submit handler
### Run `api-backed-vite`
In terminal 1:
```bash
cd examples/api-backed-vite
npm install
npm run dev:api
```
In terminal 2:
```bash
cd examples/api-backed-vite
npm run dev
```
`api-backed-vite` demonstrates:
- remote session creation (`createSession`)
- debounced draft updates (`debounceMs` + `updateRemote`)
- remote submit behavior (`submitRemote`)
- sync status and error handling (`remoteSyncStatus`, retry)
- reset/start-again behavior
- local state clearing after submit
- `remoteOnly` fields sent to the mock API but not persisted in browser storage
### Mock API behavior (`api-backed-vite/mock-server.js`)
The mock API stores drafts in memory for demo purposes and exposes generic endpoints:
- `POST /api/drafts` create a draft session
- `PATCH /api/drafts/:draftId` update a draft
- `POST /api/drafts/:draftId/submit` submit a draft
It returns non-sensitive draft metadata only and blocks updates/submissions after a draft is submitted.
### Shared/public device behavior
Both examples include a visible resume/start-again prompt:
- “We found a saved request on this device.”
- “Continue saved request or start again.”
The prompt does not display sensitive values.
Use `startAgain()` to clear persisted local state and reset funnel values. In API-backed flows, this also starts a new mock draft session.
### Example builds in CI
CI builds both example apps (`npm run build:examples`) so changes that break example integration fail quickly.
## Basic hook usage
```tsx
import { useIncrementalFunnel } from 'react-incremental-funnel';
type FunnelValues = {
fullName?: string;
email?: string;
consent?: boolean;
};
export function BasicFunnel() {
const funnel = useIncrementalFunnel<FunnelValues>({
storageKey: 'example-funnel',
steps: ['start', 'details', 'review']
});
return (
<button
onClick={() => {
funnel.updateValues({ consent: true });
funnel.nextStep();
}}
>
Continue
</button>
);
}
```
## Example integration (mock endpoints only)
```tsx
import { useIncrementalFunnel } from 'react-incremental-funnel';
type FunnelValues = {
fullName?: string;
email?: string;
consent?: boolean;
};
const mockApi = {
async createSession() {
return { sessionId: 'mock-session-id' };
},
async saveProgress(values: Partial<FunnelValues>) {
await fetch('/mock/funnel/progress', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(values)
});
},
async submit(values: Partial<FunnelValues>) {
await fetch('/mock/funnel/submit', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(values)
});
}
};
export function FunnelWithMockApi() {
const funnel = useIncrementalFunnel<
FunnelValues,
'start' | 'details' | 'review'
>({
storageKey: 'example-funnel',
steps: ['start', 'details', 'review'],
createSession: () => mockApi.createSession(),
updateRemote: values => mockApi.saveProgress(values),
submitRemote: values => mockApi.submit(values)
});
return <button onClick={() => void funnel.submit()}>Submit</button>;
}
```
## Step orchestration
Use these APIs to control progress through your funnel:
- `nextStep()` / `previousStep()` to move through `steps`
- `goToStep(stepId)` to jump to a specific step
- `markStepComplete(stepId)` / `markStepIncomplete(stepId)` for explicit completion state
- `currentStepId`, `completedStepIds`, `canGoNext`, and `canGoBack` for UI guards
- `persistStepState: true` to persist step position across sessions
- `includeStepStateInRemoteUpdate: true` to include step state in remote updates
## Field-level persistence policies
Use `fieldPolicies` to control where each field can persist:
- `local`: persist in local storage
- `session`: persist in session storage
- `memory`: persist in memory only
- `remoteOnly`: never persist locally, include only in remote updates/submission
`ttlMs` can be added per field to expire persisted values automatically.
```ts
fieldPolicies: {
fullName: { persist: 'local', ttlMs: 7 * 24 * 60 * 60 * 1000 },
email: { persist: 'session', ttlMs: 2 * 60 * 60 * 1000 },
consent: { persist: 'memory' },
temporaryInput: { persist: 'memory' },
sensitiveDraft: { persist: 'remoteOnly' }
}
```
## Storage adapters
Built-in adapters:
- `createLocalStorageAdapter()`
- `createSessionStorageAdapter()`
- `createMemoryStorageAdapter()`
Override any adapter with `storageAdapters`:
```ts
storageAdapters: {
memory: createMemoryStorageAdapter();
}
```
## Remote update callbacks
Use `updateRemote(values)` (or `remoteUpdate({ values, stepState })`) to receive debounced in-progress updates.
Pair with lifecycle callbacks:
- `onRemoteUpdateSucceeded`
- `onRemoteUpdateFailed`
Inspect `remoteSyncStatus` and `lastSuccessfulRemoteSyncAt` to drive UI status.
## Session creation callbacks
Use `createSession()` to create a server-side draft/session at funnel start.
Inspect session state with:
- `sessionCreationStatus`
- `sessionCreationError`
- `sessionMetadata`
## Submit callbacks
Use `submitRemote(values)` for final submission and call `submit()` from the hook result.
Inspect submit state with:
- `submitStatus`
- `submitError`
Lifecycle callbacks for submission:
- `onSubmitStarted`
- `onSubmitSucceeded`
- `onSubmitFailed`
## Resume / start-again handling
Use saved progress flags:
- `savedProgressExists`
- `savedProgressIsStale`
- `savedProgressMetadata`
Actions:
- `continueSavedProgress()`
- `startAgain()`
- `clearSavedProgress()` (removes persisted progress only)
## Validation callback usage
Provide per-step and full-submit validation callbacks:
```ts
validateStep: async (stepId, values) => {
if (stepId === 'details' && !values.email) {
return {
stepErrors: ['Please complete this step'],
fieldErrors: { email: 'Email is required' }
};
}
},
validateAll: async values => {
if (!values.consent) {
return {
stepErrors: ['Please accept before submitting'],
fieldErrors: { consent: 'Consent is required' }
};
}
}
```
Use `canContinueCurrentStep`, `currentStepValidationErrors`, and `fieldValidationErrors` in UI.
## Lifecycle event callbacks
You can subscribe to lifecycle events:
- `onFunnelStarted`
- `onStepStarted`
- `onStepCompleted`
- `onValuesChanged`
- `onRemoteUpdateSucceeded`
- `onRemoteUpdateFailed`
- `onSubmitStarted`
- `onSubmitSucceeded`
- `onSubmitFailed`
- `onFunnelReset`
Set `includeValuesInLifecycleCallbacks: true` only when you explicitly need values payloads.
## Shared/public device guidance
For shared/public devices:
- Prefer `session` or `memory` persistence over `local`
- Use short `ttlMs` values for persisted fields
- Mark sensitive fields as `memory` or `remoteOnly`
- Offer a visible “Start again” action that calls `startAgain()`
- Offer a visible “Clear saved progress” action that calls `clearSavedProgress()`
## Security and privacy guidance
- Do not store secrets in funnel values.
- Treat local/session storage as user-accessible and non-secret storage.
- Persist only what is required; default sensitive fields to `memory` or `remoteOnly`.
- Redact or minimize telemetry in lifecycle callbacks unless required.
- Validate and sanitize values server-side before trusting updates/submissions.
## Development
```bash
npm install
npm run lint
npm run test
npm run build
npm run build:examples
```
## Release workflow
This package uses [Changesets](https://github.com/changesets/changesets) for versioning and changelogs.
### Add a changeset in your PR
If your PR changes package behavior, add a changeset:
```bash
npm run changeset
```
Choose the bump type:
- `patch`: bug fixes and other backwards-compatible fixes.
- `minor`: backwards-compatible features.
- `major`: breaking changes.
### How releases happen
- Changes merge through pull requests into `main`.
- On pushes to `main`, the Release workflow runs `changesets/action`.
- If unreleased changesets exist, it creates or updates a release PR with:
- `package.json` version updates
- `CHANGELOG.md` updates
- consumed changesets removed
- When that release PR is merged, the same workflow publishes to npm with:
- `npm publish --provenance --access public`
- GitHub OIDC Trusted Publishing (`id-token: write`) via GitHub Actions
Do not normally run `npm publish` from a developer machine.
### Stable and prerelease channels
- Stable releases are published from `main` to the default `latest` tag (for example `1.1.0`).
- If prereleases are needed, use Changesets prerelease mode and publish with a prerelease tag such as `next` (for example `1.2.0-next.0`).
### Local package verification
Before release, verify package contents locally:
```bash
npm pack --dry-run
```
## Public API
- `createFunnel`
- `advanceFunnel`
- `useIncrementalFunnel`
- `createLocalStorageAdapter`, `createSessionStorageAdapter`, `createMemoryStorageAdapter`
- `pickPersistableValues`, `removeBlockedFields`, `redactValues`
- `FunnelStep`, `FunnelState`, `UseIncrementalFunnelOptions`, `UseIncrementalFunnelResult`, `FunnelStepId`