@radarbase/app-kit
v0.0.2
Published
A plugin-based React Native library of widgets, components, and services for building configuration-driven health research apps
Maintainers
Readme
@radarbase/app-kit
A plugin-based React Native library of SDUI nodes, core services, and configuration contracts for building manifest-driven health research apps.
The repo root is the library. A runnable host template lives in starter-kit/ and consumes the library exactly the way any other study app would — clone, rename, drop in a config, ship.
radar-base-app/ <- the library (publishable as @radarbase/app-kit)
├── src/ <- library source (TypeScript)
│ ├── core/ <- services: ApiService, ConfigService, AuthService, EventBus, ...
│ ├── library/
│ │ ├── sdui/ <- SDUI engine: SDUIShell, NodeRegistry, loaders, built-in nodes
│ │ └── contracts/ <- Zod schemas + type-only public contracts
│ └── index.ts <- public API surface
├── lib/ <- tsc build output (what consumers import)
├── starter-kit/ <- clone-and-rename host template (consumes the library)
├── docs/
├── scripts/dev.sh
├── package.json <- main: lib/index.js, types: lib/index.d.ts
└── tsconfig.json <- rootDir: src, outDir: libFeatures
- SDUI engine:
SDUIShellconsumes a manifest + per-screen blueprint JSON files and renders the UI through a node-tree walker (NodeRenderer) with per-node error isolation. - Built-in nodes: layout (
ViewNode,SectionNode,CardNode), content (TextNode,ActionNode), feature (SurveyTaskListNode,QuestionnaireNode,VitalsChartNode,ConnectDevicesMenuNode,CalendarNode), plus stubs for inbox / activity / alert nodes. - Custom nodes: register your own with
NodeRegistry.getInstance().register(...); nodes receive their blueprint slice, theme, dispatch, and template variables. - Zod-validated configs:
ManifestSchema,BlueprintSchema, andNodeSchemaguard every load. - Pluggable loaders:
ManifestLoaderandBlueprintLoaderaccept any async source (bundled JSON, remote fetch, hybrid) with primary + fallback strategies and in-memory caching. - Core services:
CoreServicesProvider+useCoreServices()forApiService,ConfigService,AuthService,EventBus,DataService, etc. - TypeScript-first: full type definitions for the public surface.
Installation (consumer)
npm install @radarbase/app-kitPeer dependencies (the host provides these — React Native projects already have most of them):
react >=16.8,react-native >=0.60- Optional peers used by certain services / built-in nodes:
@react-native-firebase/app,/analytics,/messaging,/remote-config@react-native-async-storage/async-storagereact-native-keychain
Usage
Everything is imported from the package root. You should never reach into lib/... paths.
import {
SDUIShell,
NodeRegistry,
CoreServicesProvider,
createBundledBlueprintSource,
useCoreServices,
eventBus,
} from '@radarbase/app-kit';
import type { NodeProps, CoreServiceOverrides } from '@radarbase/app-kit';Minimal host app
import React from 'react';
import {
CoreServicesProvider,
SDUIShell,
createBundledBlueprintSource,
} from '@radarbase/app-kit';
import manifest from './config/app-manifest.json';
import home from './config/views/home.json';
import insights from './config/views/insights.json';
const BUNDLED_BLUEPRINTS = {
'views/home.json': home,
'views/insights.json': insights,
};
export default function App() {
return (
<CoreServicesProvider>
<SDUIShell
manifestSource={async () => manifest}
blueprintSource={createBundledBlueprintSource(BUNDLED_BLUEPRINTS)}
/>
</CoreServicesProvider>
);
}Consuming core services from anywhere
import { useCoreServices } from '@radarbase/app-kit';
function MyNode() {
const { api, config, auth, eventBus } = useCoreServices();
// call api.get(...), config.get(...), auth.signIn(...), eventBus.emit(...)
}Registering a custom node
import { NodeRegistry } from '@radarbase/app-kit';
import type { NodeProps } from '@radarbase/app-kit';
import { Text, View } from 'react-native';
function MyCustomNode({ node, context }: NodeProps) {
return (
<View>
<Text style={{ color: context.theme.textColor }}>
{String(node.title ?? 'Hello')}
</Text>
</View>
);
}
NodeRegistry.getInstance().register('MyCustomNode', MyCustomNode);Reference it from any blueprint by "type": "MyCustomNode" — the NodeRenderer will resolve and render it. Optionally declare it in your manifest's widgetsRegistry (discovery metadata for tooling; the component must still be registered in NodeRegistry at runtime):
"widgetsRegistry": [
{ "type": "MyCustomNode", "module": "./MyCustomNode" }
]Configuration
The library is fully configuration-driven via the SDUI multi-file format. See docs/planning/SDUI_CONFIG_DESIGN.md for the full spec.
app-manifest.json— lightweight entry point: app name, theme, header, tabs (each with aviewPathpointer), secondary views, custom node registry, alerts, roles, CMS endpoints.views/*.json— per-screen blueprints, each aScreenBlueprintcontaining a node tree underroot.
Loading strategies
ManifestLoader and BlueprintLoader accept any async () => Promise<unknown> source, so hosts can plug in:
- Bundled JSON —
createBundledBlueprintSource({ 'views/home.json': home, … })for static imports (used by the starter kit today). - Remote fetch —
async (path) => (await fetch(\${cdn}/${path}`)).json()` for OTA updates. - Hybrid — a primary
source+fallbackchain (e.g. remote → bundled offline copy).
Validation against the Zod schemas runs on every load; invalid blueprints throw before they reach the renderer.
Built-in nodes
| Type | Purpose |
| --------------------------------- | ----------------------------------------------------------------------- |
| ViewNode | Root scroll container for a screen |
| SectionNode | Logical grouping with an optional header |
| CardNode | Elevated surface for a child cluster |
| TextNode | Static / interpolated text ({{user.firstName}} etc.) |
| ActionNode | Tappable button — OpenCustomView, Navigate, OpenExternalUrl, TriggerEvent |
| SurveyTaskListNode | ePRO task list (singleCard / multiCard variants) |
| QuestionnaireNode | Inline or full-page questionnaire form |
| VitalsChartNode | Single-metric chart (mini sparkline / detailed bar) |
| ConnectDevicesMenuNode | Wearable / sensor connection status |
| CalendarNode | Schedule of tasks / events (calendar / agenda variants) |
| InboxItemListCoordinatorNode | Tabbed coordinator across multiple InboxItemListNodes |
| InboxItemListNode | Filtered inbox list (stub until data layer lands) |
| RelativeActivityTodayNode | Activity progress ring (stub demo data) |
| AlertBannerNode | Inline banner (info / warning / critical) |
Development
The repo is laid out as a library + a sibling consumer app for fast feedback.
./scripts/dev.sh install # install library + starter-kit deps
./scripts/dev.sh build # compile src/ -> lib/
./scripts/dev.sh typecheck # tsc --noEmit on the library
./scripts/dev.sh starter # run the starter-kit (Expo)
./scripts/dev.sh clean # remove lib/Equivalent npm scripts at the repo root:
npm run build # tsc
npm run typecheck # tsc --noEmit
npm run clean # rm -rf lib
npm run prepublishOnly # clean + build (runs automatically on `npm publish`)Editing the library
- Edit files under
src/. - Run
npm run buildat the repo root to refreshlib/. - The
starter-kit/pins"@radarbase/app-kit": "^1.0.0". While the library is unpublished, point the starter at the workspace via either"@radarbase/app-kit": "file:../"(temporary edit, don't commit) ornpm link— seestarter-kit/README.md.
Editing the starter-kit
cd starter-kit && npm start(or./scripts/dev.sh starterfrom root).- Edit
starter-kit/App.tsxandstarter-kit/config/**/*.jsonto experiment with manifests, blueprints, and custom nodes.
Publishing
prepublishOnly runs clean && build so that npm publish ships a fresh lib/ and lib/index.d.ts. Only the lib/ directory and README.md are included in the published tarball (see the files field in package.json).
License
MIT — see LICENSE.
Contributing
- Fork the repository.
- Create a feature branch.
- Make your changes under
src/and add a usage example instarter-kit/if relevant. - Run
npm run typecheckandnpm run buildat the repo root before opening a PR. - Submit a pull request.
Support
For questions and support, please open an issue on GitHub.
