@molecule/app-react-native 1.0.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +282 -39
  2. package/package.json +6 -6
package/README.md CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-10-19T22:40:47.803Z
6
+ Generated: 2026-10-25T20:03:36.978Z
7
7
  -->
8
8
 
9
9
  # @molecule/app-react-native
@@ -105,7 +105,7 @@ Provides login/logout/register flows, token management, profile
105
105
  updates, and auth state subscription.
106
106
 
107
107
  ```typescript
108
- interface AuthClient<T = UserProfile, TRegister extends Record<string, unknown> = Record<never, never>> {
108
+ interface AuthClient<T = UserProfile, TRegister extends RegisterExtraShape<TRegister> = Record<never, never>> {
109
109
  /**
110
110
  * Returns the current authentication state snapshot.
111
111
  *
@@ -125,6 +125,15 @@ interface AuthClient<T = UserProfile, TRegister extends Record<string, unknown>
125
125
  * Gets the current user.
126
126
  */
127
127
  getUser(): T | null;
128
+ /**
129
+ * Optional ownership marker for the current session. Compare values only within
130
+ * this client: replacing or ending a session changes it, including re-adopting
131
+ * the same token and user. Profile updates and same-session refreshes keep it.
132
+ * Consumers can use it to discard stale async work without treating editable
133
+ * user fields as an immutable account identifier. Providers without this
134
+ * capability require the consumer's own stable identity check.
135
+ */
136
+ getSessionRevision?(): number;
128
137
  /**
129
138
  * Updates the cached user object (state + persistent storage) without
130
139
  * hitting the network. Intended for local refreshes after a per-app
@@ -199,6 +208,12 @@ interface AuthClient<T = UserProfile, TRegister extends Record<string, unknown>
199
208
  confirmPasswordReset(data: PasswordResetConfirm): Promise<void>;
200
209
  /**
201
210
  * Updates the current user's profile.
211
+ * Returns the saved profile: the answer applied onto the signed-in user field
212
+ * by field, with or without an `id` — a field it carries replaces the value
213
+ * (`null` clears it), one it omits or leaves `undefined` is kept, so a field
214
+ * cleared by omission must arrive as `null`. A non-object answer is returned
215
+ * as it came. An answer for a replaced or ended session leaves the current one
216
+ * untouched.
202
217
  */
203
218
  updateProfile(data: Partial<T>): Promise<T>;
204
219
  /**
@@ -254,19 +269,6 @@ interface AuthClient<T = UserProfile, TRegister extends Record<string, unknown>
254
269
  * Resolves when the session restore started by `initialize()` has settled — the point
255
270
  * after which the client knows whether this visitor is signed in.
256
271
  *
257
- * Resolves IMMEDIATELY when no restore is in flight: before `initialize()` is ever
258
- * called, and after it has finished. A restore that failed resolves too (it settled the
259
- * question), so an HTTP client can await this before every request without risking one
260
- * held forever. That is what makes the fetch-before-restore race impossible: a page that
261
- * fetches viewer-dependent data on mount waits here instead of going out anonymously
262
- * (see `setAuthGate` in `@molecule/app-http`).
263
- *
264
- * Optional: a provider that restores no session has nothing to wait for. Without it, an
265
- * HTTP client bonded to that provider sends first-paint requests unauthenticated.
266
- *
267
- * A provider whose restore sends a request THROUGH the gated HTTP client must send that
268
- * request with `skipAuthGate: true` (`RequestConfig` in `@molecule/app-http`): the gate
269
- * waits on this promise, so holding the restore's own request makes the gate wait on
270
272
  // … truncated — the full declaration is in node_modules/@molecule/app-react-native/dist
271
273
  ```
272
274
 
@@ -349,7 +351,13 @@ interface FormController<T extends Record<string, unknown> = Record<string, unkn
349
351
  */
350
352
  validate(): Promise<boolean>
351
353
  /**
352
- * Resets the form to initial values.
354
+ * Resets the form and clears errors, touched fields and submission state.
355
+ *
356
+ * With no argument it restores the current baseline — the initial
357
+ * `defaultValues`, or the values a previous `reset(values)` established.
358
+ * Passing `values` merges them over the current baseline and makes the
359
+ * result the new baseline, so a form reset to freshly loaded or saved data
360
+ * reads clean (`isDirty === false`).
353
361
  */
354
362
  reset(values?: Partial<T>): void
355
363
  /**
@@ -381,7 +389,8 @@ Form creation options.
381
389
  ```typescript
382
390
  interface FormOptions<T extends Record<string, unknown>> {
383
391
  /**
384
- * Default values.
392
+ * Default values, captured as the form's clean baseline when the controller
393
+ * is created. Mutating this object afterwards does not change the baseline.
385
394
  */
386
395
  defaultValues?: Partial<T>
387
396
  /**
@@ -467,12 +476,50 @@ interface HttpClient {
467
476
  addErrorInterceptor(interceptor: ErrorInterceptor): () => void
468
477
  /**
469
478
  * Sets the authorization token.
479
+ *
480
+ * A static token: every request carries it until the next `setAuthToken` call. A client
481
+ * that has a {@link HttpClient.setAuthTokenResolver | resolver} (registered through
482
+ * `@molecule/app-http`'s `setAuthTokenSource`) ignores this value when it assembles a
483
+ * request — the resolver IS the session, and reading it per request is what keeps a
484
+ * session adopted without an auth event from sending the previous account's bearer. Once
485
+ * the resolver is removed, the static token is sent again.
470
486
  */
471
487
  setAuthToken(token: string | null): void
472
488
  /**
473
- * Returns the current authorization token, or `null` if not set.
489
+ * Returns the token the client applies to its next request: the resolver's answer when one
490
+ * is registered, else the static token from `setAuthToken`.
474
491
  */
475
492
  getAuthToken(): string | null
493
+ /**
494
+ * Registers the LIVE token resolver every request of this client reads, or clears it with
495
+ * `null`.
496
+ *
497
+ * `@molecule/app-http`'s `setAuthTokenSource()` calls this on every client it bonds, and the
498
+ * client MUST consult the resolver again each time it assembles a request (and report it from
499
+ * `getAuthToken()`). A session can change with no auth event and no observable state change —
500
+ * `authClient.setSession()` with the same profile data, or the OAuth callback's
501
+ * `setAccessToken()` — so a token pushed only at bond time or on `login`/`logout` events
502
+ * leaves every later request carrying the PREVIOUS session's bearer while the UI shows the
503
+ * new one.
504
+ *
505
+ * The resolved token takes the static token's place, with the same precedence: an
506
+ * `Authorization` header passed per request or in the client's default headers wins over it
507
+ * exactly where it won over a static token in this transport.
508
+ *
509
+ * A resolver that returns `null` (or `undefined`) means "this session has no body token": the
510
+ * request carries no `Authorization` header of the client's own. A cookie-session API issues
511
+ * no bearer, and the server prefers the header over the fresh cookie, so a kept bearer
512
+ * authenticates as the prior — possibly deleted — session.
513
+ *
514
+ * Optional, so an implementation written against the previous interface still conforms.
515
+ * `setAuthTokenSource()` follows such a client through a request interceptor instead, pushing
516
+ * the source with `setAuthToken` before each request: that request carries the change only
517
+ * when the transport applies its token after running its interceptors, so a transport that
518
+ * applies it earlier sends a session adopted mid-page from the following request on.
519
+ *
520
+ * @param resolver - Returns the current access token (or null/undefined), or `null` to clear.
521
+ */
522
+ setAuthTokenResolver?(resolver: AuthTokenResolver | null): void
476
523
  /**
477
524
  * Registers a handler for authentication errors (401).
478
525
  *
@@ -786,12 +833,18 @@ interface UseDeviceResult {
786
833
 
787
834
  Options for useForm hook.
788
835
 
836
+ Every `FormOptions` field is supported. `createForm` is only an override —
837
+ omit it and the hook creates the controller through the bonded forms
838
+ provider (`createForm` from `@molecule/app-forms`), which uses the built-in
839
+ native provider until the app bonds its own.
840
+
789
841
  ```typescript
790
842
  interface UseFormOptions<T extends Record<string, unknown>> extends FormOptions<T> {
791
843
  /**
792
- * Form provider's createForm function.
844
+ * Explicit form factory for this one form. Defaults to the bonded forms
845
+ * provider; pass it only to pin a specific provider instance.
793
846
  */
794
- createForm: (options: FormOptions<T>) => FormController<T>
847
+ createForm?: (options: FormOptions<T>) => FormController<T>
795
848
  }
796
849
  ```
797
850
 
@@ -805,7 +858,12 @@ interface UseFormResult<T extends Record<string, unknown>> {
805
858
  isValid: boolean
806
859
  isDirty: boolean
807
860
  isSubmitting: boolean
808
- register: (name: keyof T, options?: RegisterOptions) => FieldRegistration
861
+ register: (
862
+ name: keyof T,
863
+ options?: Omit<RegisterOptions, 'name'> & {
864
+ name?: string
865
+ },
866
+ ) => FieldRegistration
809
867
  getValue: <K extends keyof T>(name: K) => T[K]
810
868
  setValue: <K extends keyof T>(name: K, value: T[K]) => void
811
869
  getError: (name: keyof T) => string | undefined
@@ -835,6 +893,13 @@ Result of useHttp hook.
835
893
 
836
894
  ```typescript
837
895
  interface UseHttpResult<T> extends UseHttpState<T> {
896
+ /**
897
+ * Runs the request and resolves with its data, or `null` on failure.
898
+ *
899
+ * This function is IDENTITY-STABLE for as long as `method`, `url` and the HTTP
900
+ * client do not change, so it is safe in a `useEffect` dependency array. Its
901
+ * request options are read when it is CALLED, from the latest committed render.
902
+ */
838
903
  execute: () => Promise<T | null>
839
904
  reset: () => void
840
905
  }
@@ -844,6 +909,11 @@ interface UseHttpResult<T> extends UseHttpState<T> {
844
909
 
845
910
  Options for useHttp hook.
846
911
 
912
+ Every request option — headers, params, body `data`, `signal`, `onSuccess`,
913
+ `onError` — is read at the moment `execute` runs, not when the hook rendered,
914
+ so an inline object or callback needs no memoizing. Only `immediate` is read
915
+ on mount.
916
+
847
917
  ```typescript
848
918
  interface UseHttpOptions<T> extends RequestConfig {
849
919
  /**
@@ -928,6 +998,80 @@ interface UsePlatformResult {
928
998
  }
929
999
  ```
930
1000
 
1001
+ #### `UseProfileFormOptions`
1002
+
1003
+ Configuration for a profile form; transport stays in the auth bond.
1004
+
1005
+ ```typescript
1006
+ interface UseProfileFormOptions<TUser, TValues extends Record<string, unknown>> extends Omit<
1007
+ UseFormOptions<TValues>,
1008
+ 'defaultValues'
1009
+ > {
1010
+ /** Complete controlled-field defaults, including blank write-only fields. */
1011
+ defaultValues: TValues
1012
+ /**
1013
+ * Optional additional account identity check; changing it discards prior edits.
1014
+ * By default ownership follows the auth client's session revision, so editable
1015
+ * fields can change during a save. Providers without getSessionRevision must
1016
+ * supply this callback with an identity that remains stable across profile edits.
1017
+ */
1018
+ getIdentity?: (user: TUser) => string
1019
+ /** Maps the current/saved user into controlled values, e.g. nullable text to ''. */
1020
+ toValues: (user: TUser) => TValues
1021
+ /**
1022
+ * Maps ONLY changed fields into an API patch. Omit blank optional credentials
1023
+ * here; preserve undefined/absent fields rather than turning them into null.
1024
+ * The second argument is the complete value snapshot taken at submit time.
1025
+ */
1026
+ toPatch: (changes: Partial<TValues>, values: Readonly<TValues>) => Partial<TUser>
1027
+ /**
1028
+ * Propagates the server's saved user to other caches, then navigates if needed.
1029
+ * Awaited before submission finishes. The auth client already persists and
1030
+ * publishes the saved user; never seed caches from the submitted patch.
1031
+ */
1032
+ onSaved?: (user: TUser) => void | Promise<void>
1033
+ }
1034
+ ```
1035
+
1036
+ #### `UseProfileFormResult`
1037
+
1038
+ Controlled profile form state and native form bindings.
1039
+
1040
+ ```typescript
1041
+ interface UseProfileFormResult<TUser, TValues extends Record<string, unknown>> extends Omit<
1042
+ UseFormResult<TValues>,
1043
+ 'register' | 'handleSubmit' | 'reset'
1044
+ > {
1045
+ /** The shared auth user's current profile. */
1046
+ user: TUser | null
1047
+ /** A user has been loaded; disable submission until this is true. */
1048
+ ready: boolean
1049
+ /**
1050
+ * The latest save/propagation failure. Rejected saves retain edits; a failed
1051
+ * onSaved callback can be retried by submit without repeating the mutation.
1052
+ */
1053
+ error: unknown
1054
+ /** Native input/textarea bindings with the field's concrete value type. */
1055
+ register: <K extends keyof TValues>(
1056
+ name: K,
1057
+ options?: Parameters<UseFormResult<TValues>['register']>[1],
1058
+ ) => Omit<FieldRegistration, 'value'> & {
1059
+ value: TValues[K]
1060
+ }
1061
+ /** Bind to the native form, with a type="submit" button inside it. */
1062
+ formProps: {
1063
+ onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
1064
+ }
1065
+ /**
1066
+ * Validates and saves one submit-time snapshot. Concurrent submits share the
1067
+ * same promise. Resolves undefined on validation, save or propagation failure.
1068
+ */
1069
+ submit: () => Promise<TUser | undefined>
1070
+ /** Discards edits and restores the newest loaded/saved profile baseline. */
1071
+ reset: () => void
1072
+ }
1073
+ ```
1074
+
931
1075
  #### `UsePromiseState`
932
1076
 
933
1077
  Extended promise state with actions.
@@ -980,11 +1124,14 @@ interface UsePushOptions {
980
1124
  Return type for useSignup hook.
981
1125
 
982
1126
  ```typescript
983
- interface UseSignupReturn<T = unknown> {
1127
+ interface UseSignupReturn<
1128
+ T = unknown,
1129
+ TRegister extends RegisterExtraShape<TRegister> = Record<never, never>,
1130
+ > {
984
1131
  status: UsePromiseState<AuthResult<T>>['status']
985
1132
  value: UsePromiseState<AuthResult<T>>['value']
986
1133
  error: UsePromiseState<AuthResult<T>>['error']
987
- signup: (data: RegisterData) => Promise<AuthResult<T>>
1134
+ signup: (data: RegisterData<TRegister>) => Promise<AuthResult<T>>
988
1135
  reset: () => void
989
1136
  }
990
1137
  ```
@@ -1271,7 +1418,10 @@ interface UseAuthOptions {
1271
1418
  Hook result for useAuth.
1272
1419
 
1273
1420
  ```typescript
1274
- interface UseAuthResult<T = unknown> {
1421
+ interface UseAuthResult<
1422
+ T = unknown,
1423
+ TRegister extends RegisterExtraShape<TRegister> = Record<never, never>,
1424
+ > {
1275
1425
  /**
1276
1426
  * The session state as one value — render from this instead of combining
1277
1427
  * `initialized`/`authenticated`/`loading`. A stored token AND user make it
@@ -1282,7 +1432,7 @@ interface UseAuthResult<T = unknown> {
1282
1432
  state: AuthState<T>
1283
1433
  login: AuthClient<T>['login']
1284
1434
  logout: AuthClient<T>['logout']
1285
- register: AuthClient<T>['register']
1435
+ register: AuthClient<T, TRegister>['register']
1286
1436
  refresh: AuthClient<T>['refresh']
1287
1437
  setUser: AuthClient<T>['setUser']
1288
1438
  /**
@@ -1293,7 +1443,7 @@ interface UseAuthResult<T = unknown> {
1293
1443
  *
1294
1444
  * `setUser` alone does NOT sign the client in, and `setAccessToken` changes
1295
1445
  * storage only — using those two for a caller-issued session leaves every
1296
- * `authenticated` read signed out (xr-molecule-flash-m178).
1446
+ * `authenticated` read signed out.
1297
1447
  */
1298
1448
  setSession: AuthClient<T>['setSession']
1299
1449
  isAuthenticated: boolean
@@ -1394,6 +1544,8 @@ Options for {@link UseChatResult.sendMessage}.
1394
1544
 
1395
1545
  ```typescript
1396
1546
  interface SendMessageOptions {
1547
+ /** Reuse a queued message's UUID when delivering it after reconnecting. */
1548
+ messageId?: string
1397
1549
  /**
1398
1550
  * Skip the optimistic local user-message bubble. The text is still sent to
1399
1551
  * the server. Used for ask_user responses: the answer is folded into the
@@ -1692,6 +1844,34 @@ How far the return-page verification has got.
1692
1844
  type VerifyPaymentReturnStatus = 'idle' | 'verifying' | 'verified' | 'failed'
1693
1845
  ```
1694
1846
 
1847
+ #### `UseTShorthandOptions`
1848
+
1849
+ Options for the shorthand form of {@link UseTFunction}: the fallback text is
1850
+ passed positionally, so only `count` remains here.
1851
+
1852
+ ```typescript
1853
+ type UseTShorthandOptions = Omit<TranslateOptions, 'defaultValue'>
1854
+ ```
1855
+
1856
+ #### `UseTFunction`
1857
+
1858
+ Translation function returned by `useT()`.
1859
+
1860
+ Two compatible call forms:
1861
+
1862
+ - `t(key, values?, options?)` — the {@link I18nProvider.t} signature.
1863
+ - `t(key, fallback, options?)` — a string second argument is the inline
1864
+ English `defaultValue`, with optional `count` for pluralization.
1865
+
1866
+ Its identity is stable while the i18n provider instance is unchanged, so it
1867
+ can be used directly as an effect dependency.
1868
+
1869
+ ```typescript
1870
+ type UseTFunction = I18nProvider['t'] & {
1871
+ (key: string, fallback: string, options?: UseTShorthandOptions): string
1872
+ }
1873
+ ```
1874
+
1695
1875
  ### Functions
1696
1876
 
1697
1877
  #### `useAppState()`
@@ -1774,8 +1954,12 @@ function useAIModels(projectId?: string): UseAIModelsResult
1774
1954
 
1775
1955
  Hook to access the auth client from context.
1776
1956
 
1957
+ Pass the same generics the client was created with —
1958
+ `useAuthClient<MyUser, { username: string }>()` — so `register()` accepts
1959
+ the app's own fields.
1960
+
1777
1961
  ```typescript
1778
- function useAuthClient(): AuthClient<T, Record<never, never>>
1962
+ function useAuthClient(): AuthClient<T, TRegister>
1779
1963
  ```
1780
1964
 
1781
1965
  **Returns:** The auth client from context
@@ -1798,8 +1982,14 @@ To create an account, use `const { register } = useAuth<MyUser>()` and
1798
1982
  `await register({ email, password, name })`. Registration returns the auth
1799
1983
  client's `AuthResult<MyUser>`; there is no separate `useRegister` hook.
1800
1984
 
1985
+ An app whose API takes its OWN registration fields passes the client's
1986
+ second generic here too — `useAuth<MyUser, { username: string }>()` — so
1987
+ `register({ email, password, username })` is typed exactly like the client
1988
+ call (and `requestBody` receives the same payload). Without it, `register`
1989
+ accepts only the base fields.
1990
+
1801
1991
  ```typescript
1802
- function useAuth(options?: UseAuthOptions): UseAuthResult<T>
1992
+ function useAuth(options?: UseAuthOptions): UseAuthResult<T, TRegister>
1803
1993
  ```
1804
1994
 
1805
1995
  - `options` — Hook options
@@ -1968,11 +2158,15 @@ function useEditor(): UseEditorResult
1968
2158
 
1969
2159
  Hook for form state management.
1970
2160
 
2161
+ Creates its controller through the bonded forms provider, so a React app
2162
+ wires a form library once (`setProvider` from `@molecule/app-forms`) and
2163
+ every form picks it up. Passing `createForm` overrides that for one form.
2164
+
1971
2165
  ```typescript
1972
- function useForm(options: UseFormOptions<T>): UseFormResult<T>
2166
+ function useForm(options?: UseFormOptions<T>): UseFormResult<T>
1973
2167
  ```
1974
2168
 
1975
- - `options` — Form options including createForm from a forms provider
2169
+ - `options` — Form options. `defaultValues`, `mode` and the form-level `validate` are forwarded to the provider; omit the options entirely for a form whose fields are registered with their own defaults.
1976
2170
 
1977
2171
  **Returns:** Form state and methods
1978
2172
 
@@ -2016,6 +2210,15 @@ function useHttpClient(): HttpClient
2016
2210
 
2017
2211
  Hook for making HTTP requests with state management.
2018
2212
 
2213
+ `execute` keeps one identity while `method`, `url` and the HTTP client stay the
2214
+ same — across the hook's own loading/data updates, parent rerenders, and inline
2215
+ options or callbacks — so `useEffect(() => { void execute() }, [execute])`
2216
+ runs once per request target instead of looping. The current headers, body,
2217
+ `signal` and callbacks are read when `execute` is called, so a later manual
2218
+ call always sends what the latest committed render passed. Changing `method`, `url` or
2219
+ the client does replace `execute` (it selects a different request) and
2220
+ re-runs such an effect.
2221
+
2019
2222
  ```typescript
2020
2223
  function useHttp(
2021
2224
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
@@ -2026,7 +2229,7 @@ function useHttp(
2026
2229
 
2027
2230
  - `method` — HTTP method
2028
2231
  - `url` — Request URL
2029
- - `options` — Request options
2232
+ - `options` — Request options, read at call time
2030
2233
 
2031
2234
  **Returns:** Request state and execute function
2032
2235
 
@@ -2231,6 +2434,29 @@ function usePreview(): UsePreviewResult
2231
2434
 
2232
2435
  **Returns:** Preview state and controls: state (url, isLoading, device, error, isConnected), setUrl, refresh, setDevice, and openExternal.
2233
2436
 
2437
+ #### `useProfileForm(options)`
2438
+
2439
+ Owns load → edit → submit → publish for the signed-in user's profile.
2440
+
2441
+ Same-account background updates refresh untouched fields but preserve dirty
2442
+ controls. Session ownership comes from the auth client, independently of mutable
2443
+ profile fields; an explicit getIdentity adds the caller's identity check.
2444
+ Submission snapshots the registered values before asynchronous
2445
+ validation, sends only changed fields through `toPatch`, and uses the auth
2446
+ client's configured updateProfile transport. The saved server response becomes
2447
+ the clean baseline; edits made while saving remain dirty. No request is made
2448
+ for a patch with no fields. A failed request retains the edits and shared user.
2449
+
2450
+ ```typescript
2451
+ function useProfileForm(
2452
+ options: UseProfileFormOptions<TUser, TValues>,
2453
+ ): UseProfileFormResult<TUser, TValues>
2454
+ ```
2455
+
2456
+ - `options` — Controlled defaults, identity and wire adapters, optional cache/navigation callback.
2457
+
2458
+ **Returns:** Form state, typed control bindings, native submit binding and save error.
2459
+
2234
2460
  #### `usePush(options)`
2235
2461
 
2236
2462
  Hook for push notification state and actions.
@@ -2323,7 +2549,7 @@ function useIsActive(path: string, exact?: boolean): boolean
2323
2549
  Hook for user registration with async state tracking.
2324
2550
 
2325
2551
  ```typescript
2326
- function useSignup(): UseSignupReturn<T>
2552
+ function useSignup(): UseSignupReturn<T, TRegister>
2327
2553
  ```
2328
2554
 
2329
2555
  **Returns:** Signup state and action
@@ -2505,15 +2731,32 @@ function useTranslation(): UseTranslationResult
2505
2731
 
2506
2732
  Hook to get just the translation function.
2507
2733
 
2734
+ The returned function keeps ONE identity for as long as the provider
2735
+ instance is unchanged, so it is safe as an effect dependency: an effect
2736
+ written as `useEffect(..., [t])` does not re-run on ordinary re-renders or on
2737
+ a locale change. The hook still re-renders the calling component on every
2738
+ locale notification (including `addTranslations` for the active locale), so
2739
+ text it reads while rendering is current. A value memoized on `[t]` alone
2740
+ keeps the old text after a locale switch: `useMemo(() => t(...), [t])`, or a
2741
+ `React.memo` child that receives `t` as a prop. Call `useT()` inside that
2742
+ child, or add `useLocale()` to the dependencies.
2743
+
2744
+ Call it in either of two compatible forms:
2745
+
2746
+ - `t(key, values?, options?)` — the provider signature from
2747
+ `@molecule/app-i18n`, e.g. `t('cart.total', { total })`.
2748
+ - `t(key, fallback, options?)` — a shorthand for the common inline-English
2749
+ call: a string second argument is the `defaultValue`, so
2750
+ `t('cart.empty', 'Your cart is empty')` equals
2751
+ `t('cart.empty', undefined, { defaultValue: 'Your cart is empty' })`.
2752
+ Interpolation and pluralization still work:
2753
+ `t('cart.items', '{{count}} items', { count: 3 })`.
2754
+
2508
2755
  ```typescript
2509
- function useT(): (
2510
- key: string,
2511
- values?: InterpolationValues,
2512
- options?: { defaultValue?: string; count?: number },
2513
- ) => string
2756
+ function useT(): UseTFunction
2514
2757
  ```
2515
2758
 
2516
- **Returns:** The translation function
2759
+ **Returns:** The translation function (stable identity while the provider is unchanged).
2517
2760
 
2518
2761
  #### `useLocale()`
2519
2762
 
@@ -2988,7 +3231,7 @@ Peer dependencies:
2988
3231
 
2989
3232
  - `@molecule/app-keyboard` ^1.0.1
2990
3233
  - `@molecule/app-lifecycle` ^1.0.1
2991
- - `@molecule/app-react` ^1.0.1
3234
+ - `@molecule/app-react` ^2.0.0
2992
3235
  - `react` ^18.0.0 || ^19.0.0
2993
3236
  - `react-native` >=0.72.0
2994
3237
  - `react-native-safe-area-context` >=4.0.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/app-react-native",
3
- "version": "1.0.3",
3
+ "version": "2.0.0",
4
4
  "description": "React Native framework bindings for molecule.dev",
5
5
  "homepage": "https://www.molecule.dev/packages/app-react-native",
6
6
  "type": "module",
@@ -33,16 +33,16 @@
33
33
  "directory": "packages/app/frameworks/react-native"
34
34
  },
35
35
  "devDependencies": {
36
- "@molecule/app-react": "1.6.7",
37
- "@types/node": "26.1.2",
38
- "react": "19.2.8",
36
+ "@molecule/app-react": "2.0.0",
37
+ "@types/node": "26.6.5",
38
+ "react": "19.3.1",
39
39
  "typescript": "6.0.3",
40
- "vitest": "4.1.11"
40
+ "vitest": "4.1.12"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@molecule/app-keyboard": "^1.0.1",
44
44
  "@molecule/app-lifecycle": "^1.0.1",
45
- "@molecule/app-react": "^1.0.1",
45
+ "@molecule/app-react": "^2.0.0",
46
46
  "react": "^18.0.0 || ^19.0.0",
47
47
  "react-native": ">=0.72.0",
48
48
  "react-native-safe-area-context": ">=4.0.0"