npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@secmia/openui-flow

v4.2.8

Published

Backend-agnostic adaptive flow engine for React apps

Readme

@secmia/openui-flow

Adaptive, graph-driven multi-step flow orchestration for React.

Build authentication, onboarding, compliance, setup, and gating flows with one engine:

  • requirement-based routing
  • dependency-aware graph evaluation
  • headless hook + default UI
  • adapter-driven backend integration
  • persistence, validation, retry/backoff

For AI agents

Use AGENTS.md for implementation debrief, architecture notes, and workflow guidance.

Install

npm install @secmia/openui-flow

Peer dependencies:

  • react ^18 || ^19
  • react-dom ^18 || ^19

3-minute quickstart (Next.js App Router safe)

Important: in Next.js App Router, functions cannot be passed from Server Components to Client Components. Keep AdaptiveFlow, adapter functions, and handlers inside a Client Component.

// app/flow-demo.tsx
'use client';

import {
  AdaptiveFlow,
  DefaultAppRequirements,
  type AdaptiveFlowAdapter,
} from '@secmia/openui-flow';

type AppRequirement =
  | (typeof DefaultAppRequirements)[keyof typeof DefaultAppRequirements]
  | 'selected_plan';

const requirements: AppRequirement[] = [
  DefaultAppRequirements.HAS_EMAIL,
  DefaultAppRequirements.EMAIL_VERIFIED,
  DefaultAppRequirements.HAS_PASSWORD,
  DefaultAppRequirements.HAS_FIRST_NAME,
  DefaultAppRequirements.HAS_LAST_NAME,
  DefaultAppRequirements.HAS_JOB_TITLE,
  DefaultAppRequirements.ACCEPTED_TOS,
  'selected_plan',
];

const adapter: AdaptiveFlowAdapter = {
  async lookupByEmail(email) {
    const exists = email.endsWith('@company.com');
    return {
      accountExists: exists,
      hasPassword: exists,
      isVerified: false,
      agreedToTos: false,
      profile: { firstName: null, lastName: null, jobTitle: null },
    };
  },
  async requestOtp(email) {
    console.log('request otp', email);
  },
  async verifyOtp(email, code) {
    console.log('verify otp', email, code);
  },
};

export default function FlowDemo() {
  return (
    <AdaptiveFlow
      requirements={requirements}
      adapter={adapter}
      persistence={{ key: 'flow-state:v1', storage: 'session' }}
      onComplete={(context) => {
        console.log('flow complete', context);
      }}
    />
  );
}
// app/page.tsx (Server Component)
import FlowDemo from './flow-demo';

export default function Page() {
  return <FlowDemo />;
}

What this package is

@secmia/openui-flow is a generic flow engine with a React-first API.

You define:

  • requirements (conditions that must be met)
  • resolvers (how each requirement is evaluated)
  • graph metadata (priority, dependency, conditional activation)

The engine decides the next step; UI can be default, partially custom, or fully headless.

Core model

  • Context: current user/product/session state.
  • Requirement: business condition (has_email, accepted_tos, etc.).
  • Resolver: maps requirement to isMet(context) + fallback step.
  • RequirementGraph: ordered nodes with priority, dependsOn, when.

Graph guarantees:

  • cycle detection at graph creation
  • dependency validation
  • dependency-aware topological ordering
  • deterministic tie-breaking

API overview

Primary exports:

  • AdaptiveFlow
  • useAdaptiveFlow
  • createRequirementGraph
  • createDefaultRequirementGraph
  • evaluateNextStep
  • getMissingRequirements
  • defaultRequirements
  • defaultRequirementResolvers
  • initialContext
  • DefaultAppRequirements
  • DefaultAdaptiveSteps

High-value types:

  • AdaptiveFlowProps
  • AdaptiveFlowAdapter
  • UseAdaptiveFlowOptions
  • UseAdaptiveFlowResult
  • AdaptiveFlowValidators
  • AdaptiveFlowSchemas
  • AdaptiveFlowPersistence
  • AdaptiveFlowRetryPolicy
  • AdaptiveFlowOAuthProvider
  • RequirementGraph
  • RequirementResolver

UI customization levels

Level 1: style/class overrides

<AdaptiveFlow
  classNames={{
    shell: 'rounded-xl border p-6',
    title: 'text-xl font-semibold',
    oauthButton: 'h-10 rounded border px-3',
  }}
  styles={{
    shell: { maxWidth: 720 },
  }}
/>

Use unstyled to disable default inline styles.

Level 2: per-step overrides (stepRegistry)

<AdaptiveFlow
  stepRegistry={{
    SELECT_PLAN: ({ setContextPatch }) => (
      <button onClick={() => setContextPatch({ selectedPlan: 'pro' })}>Select Pro</button>
    ),
  }}
/>

Level 3: full step renderer (renderStep)

<AdaptiveFlow
  renderStep={({ step, defaultView, transitions }) => (
    <section>
      <h2>{step}</h2>
      <p>Transitions: {transitions.length}</p>
      {defaultView}
    </section>
  )}
/>

Level 4: fully headless (useAdaptiveFlow)

'use client';

import { useAdaptiveFlow } from '@secmia/openui-flow';

export default function HeadlessFlow() {
  const {
    step,
    busy,
    fieldErrors,
    handleEmail,
    handleOtp,
    setContextPatch,
  } = useAdaptiveFlow({
    requirements: ['has_email', 'email_verified', 'accepted_tos'],
    completeStep: 'COMPLETE',
  });

  return (
    <div>
      <p>Step: {step}</p>
      {fieldErrors.email ? <p>{fieldErrors.email}</p> : null}
      <button disabled={busy} onClick={() => handleEmail('[email protected]')}>Submit Email</button>
      <button disabled={busy} onClick={() => handleOtp('123456')}>Submit OTP</button>
      <button onClick={() => setContextPatch({ agreedToTos: true })}>Accept TOS</button>
    </div>
  );
}

Validation and schemas

Validators can return:

  • string
  • { field, message }
  • void
  • async variants of the same
const validators = {
  password: (value: string) => {
    if (value.length < 12) {
      return { field: 'password', message: 'Password must be at least 12 characters.' };
    }
  },
};

Schema adapters are Zod-friendly (safeParse or parse):

import { z } from 'zod';

<AdaptiveFlow
  schemas={{
    email: z.string().email(),
  }}
/>

Persistence

<AdaptiveFlow
  persistence={{
    key: 'flow-state:v1',
    storage: 'local',
    clearOnComplete: true,
    onError: (error, phase) => {
      console.error('persistence', phase, error);
    },
  }}
/>

Retry/backoff (adapter calls)

<AdaptiveFlow
  retryPolicy={{
    maxAttempts: 4,
    initialDelayMs: 200,
    factor: 2,
    maxDelayMs: 2000,
    jitter: true,
    shouldRetry: (error) => error.name !== 'FlowValidationError',
  }}
/>

Notes:

  • retries apply to adapter calls (lookupByEmail, requestOtp, verifyOtp, etc.)
  • validation errors are not retried unless your shouldRetry allows them

OAuth

  • Default footer shows Google and Apple.
  • Override provider buttons with oauthProviders.
  • Redirect URL is owned by your adapter�s startOAuth implementation.
<AdaptiveFlow
  oauthProviders={[
    { id: 'google', label: 'Continue with Google' },
    { id: 'github', label: 'Continue with GitHub' },
  ]}
/>
const adapter = {
  async startOAuth(provider: string) {
    window.location.href = `/api/identity/${provider}?redirectTo=${encodeURIComponent(`${window.location.origin}/auth/callback`)}`;
  },
  async completeOAuth() {
    return { isVerified: true };
  },
};

SSR and hydration

  • Storage reads/writes run only in the browser.
  • On SSR, flow starts with defaults/initialValue and hydrates client state on mount.
  • For minimal UI flash, seed initialValue from server-known session hints.

Troubleshooting

  • Next.js "Functions cannot be passed to Client Components": keep adapter and handlers in a Client Component.
  • Step not appearing: verify priority, dependsOn, and when.
  • State reset: check persistence.key and storage mode.
  • OAuth not resuming: implement completeOAuth and enable persistence.

Release checklist

  • npm run typecheck
  • npm run build
  • Verify adapter errors are user-safe and actionable
  • Use versioned persistence keys (for example flow-state:v1)

License

MIT