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

@adaptiveapp/runtime

v0.3.0

Published

Adaptive UI SDK — the personalizable widget runtime (published to npm)

Readme

@adaptiveapp/runtime

The Adaptive UI SDK: a widget runtime whose layout is personalizable per user, per role, per breakpoint — and whose personalization can only ever subtract from what the server already allows.

Embedded in every app AdaptiveApp AI generates, and usable on its own. It has no platform coupling: it talks to a database through an interface you implement, and it works in an exported repo with the platform deleted.

pnpm add @adaptiveapp/runtime react react-dom

Peer dependencies: React ≥ 19.


The one idea

Layout is data, and resolveLayout() is the only thing that turns that data into placement.

Stored layouts arrive as a stack of layers — platform, app, org, role, team, user, device — which merge property-wise in that order. A user who moves a widget stores {x, y} and nothing else, so they cannot re-assert someone else's hidden. The resolver then filters by permission first and last, clamps to each widget's declared constraints, applies dependency fallbacks, and reflows deterministically.

That order is a security property, not a style. A stored layer, however privileged the row it came from, can never put back a widget the user may not see.

manifests + permissions + layers ─▶ resolveLayout() ─▶ EffectiveLayout

Identical inputs produce byte-identical output. Migrations, SSR and the personalization matrix all depend on it.


Quick start

import {
  AdaptiveProvider,
  AdaptiveGrid,
  defineWidget,
  createInMemoryPersistence,
} from '@adaptiveapp/runtime';
import 'react-grid-layout/css/styles.css';
import '@adaptiveapp/runtime/styles.css';

defineWidget(patientsManifest, PatientsList);

export function Dashboard({ userId, permissions }) {
  return (
    <AdaptiveProvider
      config={{
        userId,
        permissions,              // server-derived. The ceiling, not a suggestion.
        manifests,
        persistence: createInMemoryPersistence(),
        appLayoutEpoch: 1,
      }}
    >
      <AdaptiveGrid pageId="dashboard" />
    </AdaptiveProvider>
  );
}

permissions must be derived on the server from the signed-in user's role and passed in. The runtime never computes them, because a permission set the browser can edit is not a permission set.


API

Components

| Export | What it does | |---|---| | <AdaptiveProvider config store? registry?> | Creates the store once and provides it. config is read on first render only; later changes go through actions. | | <AdaptiveGrid pageId rowHeight? margin? width? breakpoint? className?> | The grid. Placement comes from resolveLayout(); react-grid-layout is an input device, never the authority. Hidden widgets are not rendered at all. | | <WidgetShell manifest placed children?> | Chrome: header, variant switcher, hide affordance, drag handle — each shown only when personalize mode and the manifest allow it. Also renders the inline selectors the resolver assigned. | | <InlineSelector slot label?> | The fallback picker. Options come from the provider widget's registered slotOptions, so hiding a provider cannot change what the slot means. |

Hooks

| Export | Returns | |---|---| | usePersonalizeMode() | { enabled, enable, disable, toggle } — the only thing that unlocks drag, resize, hide and variant switching. | | useLayoutViews() | { views, currentViewId, save, switchTo, remove, reset, undo } — named views per page. | | useSlot<T>(slotId) | [value, publish] for a context slot. | | usePublish() | publish(slotId, value), stable across renders. | | useSlotWithFallback<T>(slotId, requirement) | { value, hasProvider, strategy, inlineSelector, publish }. Implements defaultValue, preserveLast and inlineSelector. Inside <WidgetShell>, do not render inlineSelector yourself — the shell already did. | | useAdaptive(selector) | Subscribe to a slice of personalization state. | | useRunAction() | Fire a store action from a handler, routing failures to config.onError instead of an unhandled rejection. |

Registry

defineWidget(manifest, Component, {
  slotOptions: { selectedRoomId: () => fetchRooms() }, // for providers
});

Manifests are validated on registration. A widget that provides a slot should register slotOptions for it, or <InlineSelector> has nothing to offer when the widget is hidden.

Pure functions — @adaptiveapp/runtime/server

Import from the /server subpath in server components and route handlers. The main entry contains 'use client' modules, and the Next App Router refuses those from a server context.

| Export | Purpose | |---|---| | resolveLayout(input) | The resolver. Pure; safe for SSR and tests. | | runLayoutMigrations({doc, migrations, targetEpoch, manifests}) | Epoch and schema migrations. Stops at the last epoch it actually reached and says so — it never claims one it did not. | | migrateLayers(layers, args) | The same, over a whole stack. | | createAdaptiveStore(config) | The vanilla store, without React. | | createInMemoryPersistence(seed?) | Reference LayoutPersistenceAdapter — and the one the tests use. | | breakpointForWidth(px), BREAKPOINT_MIN_WIDTH | Container width → breakpoint. Note these measure the grid's container, not the viewport. | | withUserPatch, withoutUserLayer, findUserDoc, emptyUserDoc | Sparse edits to the user layer. |

Persistence

Implement LayoutPersistenceAdapter over your own storage:

interface LayoutPersistenceAdapter {
  loadLayers(key): Promise<LayoutDoc[]>;
  saveUserLayout(key, doc): Promise<void>;   // append a version, do not overwrite
  resetUserLayout(key): Promise<void>;
  undoUserLayout(key): Promise<LayoutDoc | undefined>;
  listViews({userId, pageId}): Promise<LayoutView[]>;
  saveView({userId, pageId, view}): Promise<void>;
  deleteView({userId, pageId, viewId}): Promise<void>;
  recordEvent(event): Promise<void>;
}

Two things to get right:

  • Append, don't overwrite. undo pops a version, and there is nothing to pop otherwise. Compute the next version inside the insert — read-then-write races under rapid personalization.
  • Scope by the session, server-side. If the adapter runs in the browser and posts to your API, take the user id from your verified session and ignore the one in the request.

Testing

@adaptiveapp/runtime/testing ships demo manifests covering every componentType, a createDemoEnvironment(), and an <AdaptiveHarness> that renders a whole personalizable page. The SDK's own component tests use it, and so should yours.


What personalization cannot do

The resolver enforces these, so you do not have to:

  • Show a widget whose permissions the user does not hold — filtered first and last.
  • Hide a mandatory widget, or one with hideable: false.
  • Place a widget outside its minW/maxW/minH/maxH, or off the 12-column grid.
  • Leave a visible consumer with no value: a hidden provider triggers defaultValue, preserveLast, an inlineSelector, or un-hides the provider (blockHidingProvider).
  • Produce an overlapping layout — reflow is top-left gravity in widget-id order, and a widget that still fits is never moved.

Presentation never grants power. Hiding a widget is not an access-control decision, and the API must enforce independently.


Versioning

Every stored layout carries layoutSchemaVersion (this package's doc shape) and appLayoutEpoch (your app's widget set). Bump the epoch whenever the widget set changes and register a migration; runLayoutMigrations() applies them lazily at read, and your adapter persists the result at the next write.


License

UNLICENSED — private package.