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

@signalsafe/simulator-device

v0.21.2

Published

Reusable full phone/device simulator UI composition on top of @signalsafe/simulator-react.

Readme

@signalsafe/simulator-device

0.21.2 — email layout dependency patch approved 2026-10-21

This approved metadata-only patch adopts exact theme 0.13.2 so hosts use one shared theme copy with the corrected email field/body/attachment scroll flow. Core 0.7.1, React 0.21.1, public exports and device runtime behavior are unchanged. The gallery is rebuilt with the approved theme. Publication requires genuine registry lock finalization after theme publication, all existing quality/Sonar/ packed/gallery gates, and public artifact verification.

Coordinated release — approved 2026-10-21

| Package | Prepared version | Required simulator dependencies | | --- | --- | --- | | @signalsafe/simulator-core | 0.7.1 | none | | @signalsafe/simulator-theme-bootstrap | 0.13.1 | none | | @signalsafe/simulator-react | 0.21.1 | core 0.7.1; optional default theme ^0.13.1 | | @signalsafe/simulator-device | 0.21.1 | core 0.7.1, React 0.21.1, theme 0.13.1 (exact) |

These compatible patches preserve the existing public runtime API and shared UI. React and the default theme must be adopted together: the corrected frame markup requires the matching CSS. A custom theme remains an explicit host responsibility. The optional theme peer does not install or require the default theme for unstyled integrations. Node runtime support remains >=19.0.0; build tools use Node 22/24.

Publish core and theme first, then React, then device, only after explicit approval and successful gated tag workflows. Download and verify each upstream registry artifact before refreshing downstream Yarn locks and the device demo npm lock. Future registry integrity values must never be fabricated. Before upstream publication, coherent packed-artifact installs provide candidate evidence only; frozen standalone registry installs remain a separate release gate.

Existing local host archives retain their recorded provenance until the approved registry artifacts pass all host checks. Historical release notes below describe their original checkpoints and do not authorize publication or host replacement.

Reusable full phone/device simulator UI for hosts building on @signalsafe/simulator-react and @signalsafe/simulator-core.

This package provides the complete reusable device UI composition:

  • SimulatorDevice — JSON-driven entry point (value={simulatorJson})
  • SimulatorPhoneDevice — shell + bottom nav + runtime + default incoming-call history (state/dispatch API)
  • SimulatorPhoneShell / SimulatorPhoneNav — lower-level primitives for custom layouts
  • Nav, screen-class, host-mode, and incoming-call history helpers

It does not include routing, API calls, template management, auth, or CSS. Hosts style the semantic classes from this package and @signalsafe/simulator-react in their own stylesheets.

Standalone demo

The local-apps example runs the published packages with fictional starter data, the shared theme and a Reset demo control. It requires no backend or credentials and includes a manual GitHub Pages deployment workflow. See its README for local commands, browser checks and publication steps.

Install

yarn add @signalsafe/simulator-device @signalsafe/simulator-react react react-dom

Peer dependencies: react, react-dom.

Usage

JSON-driven rendering (recommended)

Pass stored simulator JSON directly — the same full-device shape used in database/API simulator_json (entry_point, device, contacts, phone, email, messages, internet, home).

import SimulatorDevice from '@signalsafe/simulator-device/SimulatorDevice';
import type { SimulatorDevicePayload } from '@signalsafe/simulator-core/devicePayload';

function Preview({ simulatorJson }: { simulatorJson: SimulatorDevicePayload }) {
  return <SimulatorDevice value={simulatorJson} />;
}

DeliveryPlus-style flow:

  1. Load simulator_json from the database/API.
  2. Render <SimulatorDevice value={json} />.
  3. Wire onChange to persist contact edits back to the database (see editable contact detail below).

Future desktop simulator support can use the same entry point with a discriminated JSON shape; unsupported values render renderUnsupported or a safe built-in fallback.

Runtime passthrough (preview/run without manual session)

SimulatorDevice owns session state from value. Pass runtime props at the top level — they forward to SimulatorPhoneDevice → SimulatorWithSession. You do not need state / dispatch.

| Prop | Purpose | | --- | --- | | onSimulatorEvent | Structured interaction events (clicks, screen views, etc.) | | developerTools | Developer/QA panel configuration | | developerToolsTimelineEntries | Timeline entries for the developer panel | | developerToolsRuntimeIssues | Runtime lint/warning issues for the developer panel | | initialContactsSearch | Seed contacts search from deep-link query params | | exitLink, exitTo, exitLabel | Exit chrome | | compact | Embedded preview layout | | renderChoice, renderFeedback, renderContactsOverlay | Custom render slots |

Phone shell options (phone.className, phone.contactDetail, etc.) remain under phone.

import SimulatorDevice from '@signalsafe/simulator-device/SimulatorDevice';
import type { SimulatorDevicePayload } from '@signalsafe/simulator-core/devicePayload';

function RunPreview({ simulatorJson }: { simulatorJson: SimulatorDevicePayload }) {
  return (
    <SimulatorDevice
      value={simulatorJson}
      onSimulatorEvent={(event) => console.log(event)}
      developerTools={{ enabled: true, sections: { timeline: true } }}
      exitTo="/courses"
      exitLabel="Exit"
      initialContactsSearch="helpdesk"
      phone={{ contactDetail: { mode: 'editable' } }}
    />
  );
}

Editable contact detail (package-owned form)

Use phone.contactDetail for the generic package form. The host owns persistence via onChange and optional onSave / onDelete callbacks — the package does not call APIs or databases.

When onChange is provided, contact save/delete updates value.contacts immutably and calls onChange(nextValue) before host onSave / onDelete callbacks. If onChange is omitted, callbacks still fire but no JSON persistence occurs inside the package.

Omit contactDetail to use React's shared ContactDetailPanel. The same photo card and value groups also render contactDetail in read-only mode. The device owns the single bottom Back action and restores focus to the originating contact row; standalone forms retain their own Back action. renderActions={() => null} explicitly omits default form actions. Use renderContactDetail as a full escape hatch when you need completely custom UI (it takes precedence over contactDetail when both are set).

import SimulatorDevice from '@signalsafe/simulator-device/SimulatorDevice';
import type { SimulatorDevicePayload } from '@signalsafe/simulator-core/devicePayload';

function Preview({
  simulatorJson,
  setSimulatorJson,
}: {
  simulatorJson: SimulatorDevicePayload;
  setSimulatorJson: (next: SimulatorDevicePayload) => void;
}) {
  return (
    <SimulatorDevice
      value={simulatorJson}
      onChange={setSimulatorJson}
      phone={{
        contactDetail: {
          mode: 'editable',
          onSave: (contact) => console.log('saved', contact),
          onDelete: (contact) => console.log('deleted', contact),
        },
      }}
    />
  );
}

Custom fields via renderExtraFields

import SimulatorDevice from '@signalsafe/simulator-device/SimulatorDevice';
import type { SimulatorDevicePayload } from '@signalsafe/simulator-core/devicePayload';
import type { SimulatorPhoneContactDetailContext } from '@signalsafe/simulator-device/contact/contactDetailTypes';
import type { SimulatorPhoneContactDetailValues } from '@signalsafe/simulator-device/contact/contactDetailTypes';

function ExtraFields({
  contact,
  updateContact,
}: {
  contact: SimulatorPhoneContactDetailValues;
  updateContact: (patch: Partial<SimulatorPhoneContactDetailValues>) => void;
  context: SimulatorPhoneContactDetailContext;
}) {
  return (
    <label>
      Notes
      <input
        value={contact.displayName}
        onChange={(event) => updateContact({ displayName: event.target.value })}
      />
    </label>
  );
}

function Preview({ value, onChange }: { value: SimulatorDevicePayload; onChange: (v: SimulatorDevicePayload) => void }) {
  return (
    <SimulatorDevice
      value={value}
      onChange={onChange}
      phone={{
        contactDetail: {
          mode: 'editable',
          renderExtraFields: (props) => <ExtraFields {...props} />,
        },
      }}
    />
  );
}

Fully custom contact detail (renderContactDetail)

import SimulatorDevice from '@signalsafe/simulator-device/SimulatorDevice';
import type { SimulatorDevicePayload } from '@signalsafe/simulator-core/devicePayload';

function Preview({
  simulatorJson,
  onSave,
}: {
  simulatorJson: SimulatorDevicePayload;
  onSave: (next: SimulatorDevicePayload) => void;
}) {
  return (
    <SimulatorDevice
      value={simulatorJson}
      onChange={onSave}
      phone={{
        renderContactDetail: ({ contact, onBack }) => (
          <div>
            <button type="button" onClick={onBack}>Back</button>
            <h1>{contact.displayName}</h1>
          </div>
        ),
      }}
    />
  );
}

Advanced phone-only state/dispatch usage

When you already manage session state (preview hosts, timeline tooling, deep links), use SimulatorPhoneDevice directly with getInitialSessionState and simulatorSessionReducerWithLogging from @signalsafe/simulator-react.

Basic full phone UI

SimulatorPhoneDevice is the default entry point for a complete phone simulator preview or embed. No CSS framework is included — target semantic classes in your host CSS.

import SimulatorPhoneDevice from '@signalsafe/simulator-device/SimulatorPhoneDevice';
import { SIMULATOR_DEVICE_CLASS_NAMES } from '@signalsafe/simulator-device/simulatorDeviceClasses';
import { SIMULATOR_DEVICE_SHELL_SCREEN_CLASS_NAMES } from '@signalsafe/simulator-device/simulatorPhoneShellScreenMapper';
import { getInitialSessionState } from '@signalsafe/simulator-react/state/simulatorSessionInitialState';
import { SimulatorDispatchAction } from '@signalsafe/simulator-react/state/simulatorDispatchActions';
import { SimulatorSessionState } from '@signalsafe/simulator-react/types/session';

/** Host CSS targets `.simulator-device-shell`, `.simulator-device-nav`, etc. */
void SIMULATOR_DEVICE_CLASS_NAMES;
void SIMULATOR_DEVICE_SHELL_SCREEN_CLASS_NAMES;

function PhonePreview({
  state,
  dispatch,
}: {
  state: SimulatorSessionState;
  dispatch: (action: SimulatorDispatchAction) => void;
}) {
  return <SimulatorPhoneDevice state={state} dispatch={dispatch} />;
}

const state = getInitialSessionState(templatePayload);

Custom host-owned contact detail

Option A — package generic form (contactDetail):

import SimulatorPhoneDevice from '@signalsafe/simulator-device/SimulatorPhoneDevice';
import type { SimulatorDispatchAction } from '@signalsafe/simulator-react/state/simulatorDispatchActions';
import type { SimulatorSessionState } from '@signalsafe/simulator-react/types/session';

function PhonePreview({
  state,
  dispatch,
}: {
  state: SimulatorSessionState;
  dispatch: (action: SimulatorDispatchAction) => void;
}) {
  return (
    <SimulatorPhoneDevice
      state={state}
      dispatch={dispatch}
      contactDetail={{
        mode: 'editable',
        onSave: (contact) => console.log('save', contact),
        onDelete: (contact) => console.log('delete', contact),
      }}
    />
  );
}

Option B — full escape hatch (renderContactDetail):

Pass renderContactDetail to replace the contact detail view with your own UI. The device shell applies simulator-device-shell--screen-phone-contact-detail while the overlay is active. When both renderContactDetail and contactDetail are set, renderContactDetail wins.

import SimulatorPhoneDevice from '@signalsafe/simulator-device/SimulatorPhoneDevice';
import type { SimulatorDispatchAction } from '@signalsafe/simulator-react/state/simulatorDispatchActions';
import type { SimulatorSessionState } from '@signalsafe/simulator-react/types/session';

function PhonePreview({
  state,
  dispatch,
}: {
  state: SimulatorSessionState;
  dispatch: (action: SimulatorDispatchAction) => void;
}) {
  return (
    <SimulatorPhoneDevice
      state={state}
      dispatch={dispatch}
      className="my-simulator-root"
      screenClassNames={['my-simulator-root--embedded']}
      renderContactDetail={({ contact, onBack }) => (
        <div className="my-contact-detail">
          <button type="button" onClick={onBack}>
            Back
          </button>
          <h1>{contact.displayName}</h1>
          <p>{contact.number}</p>
        </div>
      )}
    />
  );
}

onBack clears the host selection and returns to the simulator runtime.

Manual shell composition

For lower-level control, compose primitives directly:

import SimulatorPhoneShell from '@signalsafe/simulator-device/SimulatorPhoneShell';
import SimulatorPhoneNav from '@signalsafe/simulator-device/SimulatorPhoneNav';
import { shouldHideHostPhoneNav } from '@signalsafe/simulator-device/simulatorPhoneNavMapper';
import { SIMULATOR_DEVICE_CLASS_NAMES } from '@signalsafe/simulator-device/simulatorDeviceClasses';
import SimulatorWithSession from '@signalsafe/simulator-react/SimulatorWithSession';
import { SimulatorSessionState } from '@signalsafe/simulator-react/types/session';

void SIMULATOR_DEVICE_CLASS_NAMES;

function DevicePreview({ state, dispatch }: { state: SimulatorSessionState; dispatch: DispatchFn }) {
  return (
    <SimulatorPhoneShell
      useHostNav={!shouldHideHostPhoneNav(state)}
      nav={shouldHideHostPhoneNav(state) ? null : <SimulatorPhoneNav state={state} dispatch={dispatch} />}
    >
      <SimulatorWithSession state={state} dispatch={dispatch} />
    </SimulatorPhoneShell>
  );
}

Public API

| Export | Description | | --- | --- | | SimulatorDevice | JSON-driven entry point; owns session from SimulatorDevicePayload | | SimulatorDeviceProps | Props for JSON-driven device rendering | | SimulatorDeviceRuntimePassthroughProps | Runtime props forwarded to SimulatorWithSession (events, dev tools, deep-link search) | | SimulatorDevicePhoneOptions | Optional phone overrides passed through to SimulatorPhoneDevice | | resolveSimulatorDeviceKind | Classify supported vs future/unsupported JSON shapes | | SimulatorPhoneDevice | Composed phone shell + nav + runtime + optional host contact detail | | SimulatorPhoneDeviceProps | Props for the composed phone device | | SimulatorPhoneDeviceContactDetailRenderProps | renderContactDetail callback context | | SimulatorPhoneContactDetailForm | Generic contact detail form (displayName, number, email) | | SimulatorPhoneContactDetailFormProps | Props for the contact detail form | | SimulatorPhoneContactDetailValues | Contact value shape for save/delete callbacks | | SimulatorPhoneDeviceContactDetailOptions | contactDetail options for SimulatorPhoneDevice / SimulatorDevice | | SimulatorPhoneContactDetailContext | Save/delete callback context (state, dispatch, originalContact) | | contactSnapshotFromSessionContact | Map session contact → contact detail values | | splitContactDisplayName | Split display name into first/last parts | | patchContactInDevicePayload | Immutably update one contact in SimulatorDevicePayload | | removeContactFromDevicePayload | Immutably remove one contact from SimulatorDevicePayload | | SimulatorPhoneShell | Presentational device shell | | SimulatorPhoneNav | Session-driven bottom navigation | | SimulatorPhoneNavItem | Single nav button (for custom layouts) | | resolveSimulatorPhoneNav | Derive primary/secondary/hidden nav model from session state | | dispatchSimulatorPhoneNavItem | Map nav item clicks to simulator dispatch actions | | shouldHideHostPhoneNav | Hide nav on thread/email detail screens | | SIMULATOR_PRIMARY_NAV_ITEMS | Primary tab metadata | | SIMULATOR_DEVICE_CLASS_NAMES | BEM class constants for host styling | | SIMULATOR_DEVICE_SHELL_SCREEN_CLASS_NAMES | Shell screen modifier class strings for host CSS | | resolveSimulatorPhoneShellScreenClasses | Derive screenClassNames for SimulatorPhoneShell from session view | | resolveSimulatorPhoneShellHostMode | Host overlay mode (e.g. phone contact edit) from session + selected contact id | | SimulatorPhoneShellHostMode | Host overlay mode type | | SimulatorPhoneNavModel, SimulatorPhoneNavItemModel | Nav model types | | SimulatorPhoneIncomingCallHistory | Previous-calls table for incoming-call screen | | renderPhoneIncomingCallHistoryExtra | Drop-in renderIncomingCallExtra slot for @signalsafe/simulator-react | | normalizePhoneNumber, resolveIncomingCallCaller, getRecentCallsForCaller | Incoming-call history helpers | | PhoneIncomingCallHistoryRow, PhoneIncomingCallCaller | History row and caller types |

Types for session state and dispatch actions come from @signalsafe/simulator-react.

Incoming-call previous calls

SimulatorPhoneDevice passes renderPhoneIncomingCallHistoryExtra to SimulatorWithSession by default. Override with renderIncomingCallExtra when needed.

The history table uses the semantic class simulator-phone__incoming-call-history from @signalsafe/simulator-react. Host apps style that class in their own CSS — this package does not ship stylesheets.

When no matching recent calls exist for the active caller, the slot returns null (no empty wrappers).

Each row's time is formatted with the host's regional preferences (date and time format, via simulator-react contract/regionalPresentation) instead of showing the raw ISO timestamp.

Development

yarn install
yarn typecheck
yarn test
yarn build
yarn smoke:package

License

MIT

Navigation interception

SimulatorDevice, SimulatorPhoneDevice, and standalone SimulatorPhoneNav accept the additive synchronous onNavigation callback and observational onNavigationEvent. Return 'handled' to leave the package state/back stack untouched and display a host-owned destination; return 'delegate' or nothing for package navigation. Composed devices apply the boundary once across their menu and nested session. Existing onSimulatorEvent remains analytics only. Secondary Contacts Back now returns to the primary menu consistently with simulator-react, without artificial history/contacts screen hops.

The full contract and release sequence are in simulator-react's docs/navigation-contract.md. Release simulator-react first and raise this package's dependency minimum to that new version before releasing device. The package manifest declares the compatible simulator-core, simulator-react and default theme versions. Hosts own provider-specific Settings controls through slots.

Host screen content

SimulatorPhoneDevice inherits screenOverrides from SimulatorWithSession. For example, { home: { settings: HostSettings } } replaces Settings content inside the existing shell and navigation, without intercepting action variants. Stable component types receive typed session/location, intercepted dispatch, Back and lazy default rendering. See sibling simulator-react docs/screen-overrides.md for fallback, lifecycle, accessibility and ordered release/adoption requirements. Existing contact-detail slots retain their precedence; host integration remains application-owned.

Datasource entry point (0.4)

import { createSimulatorDatasource } from '@signalsafe/simulator-react/datasource/datasource';
import SimulatorDevice from '@signalsafe/simulator-device/SimulatorDevice';
const datasource = createSimulatorDatasource(jsonSource);
<SimulatorDevice datasource={datasource} />;
// Existing consumers remain valid:
<SimulatorDevice value={jsonSource} />;

Supply exactly one source. Both properties are a type error and throw at runtime. Datasource replacement preserves navigation and mounted drafts, reconciling removed email selections. Replacing editable value retains its session-reset behavior. SimulatorPhoneDevice also accepts an optional datasource alongside its existing controlled state/onAction contract; the host remains responsible for session actions and resets. JSON-only consumers require no transport or callbacks. See the React package's datasource contract for source validation, full-device conventions, readonly snapshots and API ownership.

Datasource conversion ownership (0.5)

SimulatorDevice and SimulatorPhoneDevice memoize the mutable session copy by datasource identity. Keep that identity stable while navigating and replace it when content changes. Controlled hosts that already compose state.payload should pass state without an additional datasource overlay. JSON-only consumers retain their existing normalization and replacement/reset behavior. Datasource snapshots now use simulator-react 0.4 deeply readonly types; edit a mutable session copy or create a replacement snapshot.

Labeled contact values

Session contacts accept phoneNumbers and emailAddresses arrays of { label, value, number? }. value is display/original text; number is an optional canonical dialing value supplied by the host. Empty labels display as “Unlabeled”. Existing number and email fields remain supported when arrays are absent. Editable contact details support adding, editing, and removing rows; changing a phone value clears its canonical number until the host normalizes it. contactDetail.renderPhoneAction(phone, contact) supplies an optional action for each phone row. The simulator does not place external calls itself. Full-device payloads use phone_numbers and email_addresses; adapters preserve these arrays when saving and reopening a contact.

Appearance settings

SimulatorAppearanceSettings provides a controlled appearance editor with presets, background/accent pickers, and an isolated preview. Hosts supply value, presets, onApply, and onReset, and own persistence and token mapping. Draft changes do not apply until the user presses Apply. Color inputs use opaque six-digit hex values; appearanceTextColor chooses a contrasting black/white foreground.

Appearance also accepts optional backgroundImage (a raster image data URL). The image picker reads local PNG/JPEG/WebP files up to 2 MiB, previews them, and supports removal. The host persists the image alongside colors and maps it to --simulator-background-image; the theme centers and covers the screen.

Controlled workflows and availability

SimulatorPhoneDevice accepts emailCompose, messageCompose, dialDraft, and capabilities. Drafts remain host-owned when supplied. Email and new-message callbacks may return a promise: rejection preserves the draft, successful acceptance clears it, and duplicate submission is blocked while pending. These callbacks do not configure an external provider.

capabilities uses SimulatorActionCapabilities from simulator-react. Each action is either { state: 'enabled' } or { state: 'unsupported' | 'unavailable', reason }. Reasons are visible in the screen/navigation. Contact save/delete callbacks also support promises and preserve the draft on rejection. Closing contact details restores keyboard focus to the originating row when it is still present.

Synthetic UI gallery

Run npm run preview:gallery, then open http://127.0.0.1:5176. npm run build:gallery produces a static gallery; tsc -p tsconfig.gallery.json checks its fixtures. The gallery uses the installed shared React package and theme, without PhoneMe APIs or external providers. It covers populated, empty, loading, unavailable, and error states; light/dark/high-contrast tokens; 320–520 pixel widths; and 100–200% text size. Screen controls cover contacts, contact editing, history, dialing, messages, and email compose. Error-mode email submission deliberately rejects. These fixtures supplement real host, browser, device and live-provider acceptance.

The npm archive includes gallery-dist/, a self-contained static build that can be served without sibling repositories. Gallery development commands above run from this source repository.

Release records

See CHANGELOG.md and RELEASING.md. Runtime dependencies are pinned in package.json, with React 18 peers. Release checks require published dependencies (no file: or link: pins). No sibling source checkout is required.

Development tooling requires Node 22.22.2+ or Node 24.15+ (jsdom 30); CI selects current Node 22/24. The published runtime retains its Node >=19.0.0 contract and React 18 peers. TypeScript 7, Vite 8 and Vitest 5 are build/test tools, not runtime dependencies.

Node runtime compatibility

The runtime requirement is Node >=19.0.0. Build, unit-test and coverage tools use Node 22/24 (use Node 24.16+ locally). A separate CI job installs packed artifacts with strict engine checks and tests runtime behavior on Node 19.0.0 and 19–24.

The compatibility job builds this package and installs its declared dependencies from npm with strict engine checks. Before publication, confirm every dependency is a released version and run yarn check:release. Regenerate each downstream lockfile after its upstream releases are available. No sibling source overrides are used in the runtime matrix.

Dependency revisions and coverage

Use the exact simulator versions declared in package.json and adopt React with its matching default theme. The runtime matrix installs these registry releases through the packed device manifest, including their TreeSpec dependency.

Run yarn test:coverage to check the unit suite. CI requires 100% statements, branches, functions, and lines across the existing source coverage scope.

Historical local app migration (0.17.0, release candidate)

  • Export SimulatorDeviceApps, SimulatorDeviceAppsProvider and SimulatorCallBoundary for Home, local apps, locking and call overlays that preserve mounted drafts.
  • Delegate app navigation items to the shared React primitive while preserving existing device CSS hooks.
  • Include a strict React 18 example with an in-memory host adapter and browser workflow test, importing only public package APIs.
  • Require core 0.4.1 and React 0.17.0; retain React 18 peers. Theme 0.10.0 is used by the gallery/example.

Use the matching registry version after its release workflow completes. See RELEASING.md for the coordinated release order and consumer validation. Installed package files are never patched.

Shared app identifiers

Import SimulatorApp and isSimulatorApp directly from @signalsafe/simulator-core. Use SimulatorApp.Phone, .Email, .Messages, .Internet, and .Home for app IDs. The frozen enum-style object also supplies the SimulatorApp string-union type; existing JSON values stay compatible. Use isSimulatorApp(value) at untrusted boundaries and Object.values(SimulatorApp) when enumerating all apps. Do not confuse app IDs with channels (sms, browser, contacts), screen names, or contact/input field kinds.

See AGENTS.md for module ownership and verification rules. Use the explicit declaring-module paths shown in the examples; root imports are not supported. Historical local archive adoption remains recorded by each host.

Shared device settings

SimulatorDeviceApps.settings opts into the shared Settings page. Pass controlled appearance props and a regional adapter that renders simulator-react/apps/settings/RegionalSettings; the device supplies its store, locking and Back-to-Home navigation. SimulatorDeviceSettings is also available directly at ./apps/SimulatorDeviceSettings, with optional onBack when a surrounding host does not already own navigation. Screen-password controls are included automatically; children accepts explicit host features. PhoneMe adds the shared simulator-react/apps/settings/DeviceBackup form through this slot alongside its blocking and contact-merge tools. These three sections are absent from the portable demo. Preference persistence stays with the host.

The appearance form defaults to localized shared presets and Reset restores Night. Supply resetValue when using host-defined presets with a different reset policy. Palette values and custom CSS-variable mapping live in simulator-theme-bootstrap/appearance; that pure data entry imports no CSS. Hosts still import the theme stylesheet explicitly.