Build an app for Jinnie.
One package, one manifest entry, one exposed screen. The host renders your default export inside its own scroll container, app bar, safe area and error boundary, so what you write is content rather than a screen.
1. Create the package
Copy an existing community app. packages/remit is the smallest useful template. A mini app is:
packages/hello/
package.json name: @jinnie/hello
rspack.config.mjs container name 'hello', exposes './App', devServer port
babel.config.js react-native preset
react-native.config.js
index.js standalone entry (dev only)
app.json
src/App.tsx the default export the host rendersThe container name in rspack.config.mjs and the id in the host registry must match. That pairing is what resolves hello/App at runtime.
2. Write the screen
import React from 'react';
import {Card, Body, useHost} from '@jinnie/sdk';
export default function HelloApp() {
const host = useHost();
return (
<Card>
<Body>
Hello from {host.app.name}. {host.online ? 'Online.' : 'Offline.'}
</Body>
</Card>
);
}3. Register it with the host
Two edits in the host package: a port for the dev server, and one manifest entry.
// rspack.config.mjs — MINI_APPS
hello: 9014,// src/registry.ts
{
id: 'hello',
name: 'Hello',
description: 'My first mini app.',
icon: IconGrid,
tone: 'violet',
kind: 'community',
author: 'Your Name',
version: '1.0.0',
}The host loads hello/App through the federation runtime by name, so there is nothing else to declare. Add it to the dev dashboard and start everything.
The bridge: useHost()
The only way a mini app may talk to the host.
| Member | Type | Notes |
|---|---|---|
app | {id, name, version} | Your manifest identity |
user | {id, name} | null | null until host sign-in ships |
online | boolean | Live connectivity |
currency | string | 'PHP' |
storage | get / set / remove | Async. Namespaced to your app id |
finance | HostFinance? | Core apps only. undefined for community apps |
close() | () => void | Return to the launcher |
HostFinance exposes the balance, the base currency, active accounts, normalised asset, liability, and net-worth totals, the read-only Wallet transactions, and four mutations:
addTransaction({type, amount, description, category, accountId?, billId?, billPeriod?})
transferBetweenAccounts({amount, description, fromAccountId, toAccountId})
updateTransaction(id, changes)
deleteTransaction(id)Transfers are neutral to cash flow and move both linked balances. Linked account balances and bill-payment status are reconciled when an entry is edited or deleted. Mutations reject non-finite or non-positive amounts, blank descriptions, unknown or archived account ids, and same-account transfers. The bridge and the regression suite call the same implementation, so a test that passes is a statement about the app, not about a copy of it.
Public catalogs
Read-only platform metadata, safe for any app. Never a user’s balances.
import {
catalogs, financialProviders, transactionCategories, billers, currencies,
ProviderPicker, CategorySelect, BillerPicker, CurrencyPicker, AccountPicker,
} from '@jinnie/sdk';Persist the id, not the display label, so copy and localisation can change safely. Every catalog picker ends with Add new; a name the user types there is saved once and offered in every picker of that kind from then on. Every category has a glyph, and institutions and billers draw a bundled logo when one exists and a monogram otherwise. Community apps may use the provider picker; the finance context stays withheld regardless.
UI kit
Everything in the SDK’s UI folder is exported from @jinnie/sdk: plain StyleSheet on top of the theme tokens, no UI framework, nothing a mini app needs to restyle.
| Group | Components |
|---|---|
| Layout | Stack, Row, Card, CardTitle, SectionHeader, AppBar |
| Content | Body, Muted, Stat, Badge, Progress, InlineAlert, EmptyState, Skeleton |
| Controls | Button, IconButton, PressableCard, Input, Textarea, Label, Switch, Segmented, SearchInput, Select |
| Tokens | useTheme, useIsDark, space, radius, tnum, toneColors, withAlpha |
Rules
- Import only from
@jinnie/sdk,react, andreact-native. Never reach into another app’s package or into the host. - No native dependencies. The host owns them. If you truly need one, it has to be added to the host as a shared singleton, which means a host release. That is the cost the architecture is designed to make visible.
- Offline-first. Anything you persist goes through
host.storageor a store in the SDK. Any network call must degrade gracefully and disclose what it sends. - No colour literals.
useTheme()or a named tone. Both schemes must look deliberate. - Every tap target is at least 44pt, and every icon-only control has a label. The SDK components enforce this; do not hand-roll around them.
- One sheet at a time. A select inside a sheet opens as a page of that sheet. Never render a sheet while another is open.
- English copy, professional tone, no emoji. Use the icon set.
Testing
Finance logic lives in the SDK and runs under plain Node with in-memory storage shims. Add cases there for anything that touches money. Screens are not unit-tested; verify those on a device.
pnpm test # finance calculations and data-integrity regressions
pnpm typecheck # every packageDeploying a mini app
Build the container and serve its manifest and bundle from any static origin.
pnpm --filter @jinnie/hello bundle:androidPoint the remote host at that origin. Because the host resolves remotes at runtime, publishing a new bundle updates the app for existing installs without an app-store release.
Questions about the SDK go to hello@jinnie.ph.