@pagehub-io/ux
v0.1.2
Published
Shared cross-app UX for the pagehub-io fleet. Ships the embedded SupportWidget, lifted ticket primitives, and design tokens.
Readme
@pagehub-io/ux
Shared cross-app UX for the pagehub-io fleet. v0.1 ships the embedded
SupportWidget that any app-* (app-prayers, app-serve,
app-wild-rooted-bakery, future apps) drops into its layout to give end
users a one-tap support channel that talks to the pagehub backend.
The standalone pagehub site uses native ticket screens for full-fidelity
admin UX; this package is for cross-app embeds (per
pagehub/SPEC.md Architect §Integrations).
Install
npm install @pagehub-io/ux react react-native expo @expo/vector-icons react-native-safe-area-contextreact, react-native, expo, @expo/vector-icons, and
react-native-safe-area-context are peer deps; the host app provides them.
Supported versions
Peer ranges are intentionally permissive (>=) but only the versions below
are exercised by npm test / npm run typecheck / npm run build in CI.
Hosts on older majors may work but are not contractually supported until a
fleet app reports a green smoke test.
| Peer dep | Range | Tested against |
|-----------------------------------|------------------|----------------|
| react | >=18.0.0 | 19.1.0 |
| react-native | >=0.74.0 | 0.81.5 |
| expo | >=51.0.0 | 54.0.32 |
| @expo/vector-icons | >=14.0.0 | 15.1.1 |
| react-native-safe-area-context | >=4.10.0 | 5.6.0 |
Quickstart
import { SupportWidget } from '@pagehub-io/ux';
import { SafeAreaProvider } from 'react-native-safe-area-context';
async function getJwt(): Promise<string> {
return await myAuthClient.currentAccessToken();
}
export default function App() {
return (
<SafeAreaProvider>
<YourHostApp />
<SupportWidget
apiBaseUrl="https://pagehub.example.com"
authToken={getJwt}
onUnreadCountChange={(n) => myStore.setSupportBadge(n)}
/>
</SafeAreaProvider>
);
}The widget renders its own floating bubble (bottom-right, 16px from each
edge, safe-area aware). It does NOT take an open / onClose prop in v1
(host-controlled trigger is v2 — see pagehub/CLAUDE.md locked decision
#5).
Auth contract
authToken: () => Promise<string>;The host provides a callable that resolves to the user's pagehub-auth JWT.
The widget invokes it on every outbound request. On a 401 response,
the widget invokes authToken() again to obtain a fresh JWT and retries
the original request once with the same Idempotency-Key. This is the
entire token-refresh contract — no separate jwt-refresh callback.
If the second attempt also returns 401, the widget surfaces the "You've been signed out" error state, with the host expected to drive the user back through pagehub-auth's hosted login.
Viewport shapes
Internal viewport detection picks one of three expanded shapes when the bubble is tapped:
| Width range | Shape | Implementation |
|--------------|---------------|----------------------------------------------|
| <= 767 | mobile sheet | full-screen Modal, slide-up 300ms |
| 768..1023 | tablet drawer | 400px right-edge Modal, slide-right 300ms |
| >= 1024 | inline panel | 360x600 anchored bottom-right, fade+scale 200ms |
The bubble itself is always present; only the expanded surface changes.
Backend prerequisites
- pagehub
/ticketsREST surface — seepagehub/SPEC.mdArchitect Contract #3. - pagehub-auth issued JWT with
app_slugclaim — pagehub accepts JWTs from any registeredapp_slug, not just its own (locked decision #1 — the architectural inversion that makes this widget work cross-app).
Notifications
- Numeric badge on the bubble when
total > 0fromGET /tickets/unread-summary. Max display is9+. - No toast on widget mount, ever. This is locked decision #8 — the bubble badge is the only notification channel.
- Optional
onUnreadCountChangecallback fires whenever the polled total changes — hosts may render their own additional badge (e.g.,app-prayersmirrors the count in its drawer's "More" item).
Versioning
0.1.0 (v1):
- Floating-bubble trigger only (no host-controlled imperative trigger)
- Polling-based unread refresh (30s base, AppState-paused, exponential backoff up to 5min). SSE / push is v2.
- No email or SMS notifications — deferred to v2 entirely.
See pagehub/SPEC.md and pagehub/CLAUDE.md for the full set of locked
design decisions.
Local development
cd ~/github/pagehub-io/platform/ux
npm install
npm run typecheck
npm run build # produces dist/index.{js,mjs,d.ts}For a runtime smoke test, see examples/App.tsx. To consume locally
inside another package, use a workspace-linked file: dep:
// host package.json
{
"dependencies": {
"@pagehub-io/ux": "file:../platform/ux"
}
}(Per Architect §Integrations: pagehub mobile pins via file: until the
embed targets adopt the published version.)
