@everfur/sdk 0.7.1 → 0.9.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 (185) hide show
  1. package/CHANGELOG.md +350 -0
  2. package/README.md +24 -16
  3. package/dist/{Chat-DQOsAumH.d.cts → Chat-Cb3Y8VwS.d.cts} +3 -3
  4. package/dist/{Chat-CFi5cNUF.d.ts → Chat-ClhB_8sp.d.ts} +3 -3
  5. package/dist/{DepthViews-D81c95Ra.d.cts → DepthViews-BSkuUGBB.d.ts} +1 -1
  6. package/dist/{DepthViews-C409ubw-.d.cts → DepthViews-CSCS-tZg.d.ts} +1 -1
  7. package/dist/{DepthViews-es8mIK0w.d.ts → DepthViews-CW1z8pnl.d.cts} +1 -1
  8. package/dist/{DepthViews-uilL1NJZ.d.ts → DepthViews-DDiPxyzg.d.cts} +1 -1
  9. package/dist/{ErrorPolicyPort-D22LUVrK.d.ts → ErrorPolicyPort-DwSnmGcX.d.ts} +1 -1
  10. package/dist/{ErrorPolicyPort-D6CybiqJ.d.cts → ErrorPolicyPort-NFDPe-5w.d.cts} +1 -1
  11. package/dist/{EverfurError-DmELDpXp.d.cts → EverfurError-pEXDbUJ1.d.cts} +9 -1
  12. package/dist/{EverfurError-DmELDpXp.d.ts → EverfurError-pEXDbUJ1.d.ts} +9 -1
  13. package/dist/{EverfurResult-ZtH-YV1h.d.cts → EverfurResult-dtl8ERAw.d.cts} +1 -1
  14. package/dist/{EverfurResult-SGvHKsCB.d.ts → EverfurResult-sKkoXew0.d.ts} +1 -1
  15. package/dist/IntakeMedia-BFZfBLjB.d.cts +116 -0
  16. package/dist/{IntakeMedia-BcjjUizr.d.ts → IntakeMedia-BH0fYDT2.d.cts} +19 -2
  17. package/dist/{IntakeMedia-CL_S2tZE.d.cts → IntakeMedia-Bavl7E4Z.d.ts} +19 -2
  18. package/dist/IntakeMedia-C8GlN_GK.d.ts +116 -0
  19. package/dist/animations/index.cjs +109 -1
  20. package/dist/animations/index.d.cts +6 -6
  21. package/dist/animations/index.d.ts +6 -6
  22. package/dist/animations/index.js +109 -1
  23. package/dist/{attachments-B2GjBgvF.d.cts → attachments-BD70jZZL.d.cts} +3 -3
  24. package/dist/{attachments-SF88N654.d.ts → attachments-CC7aHpwZ.d.ts} +7 -7
  25. package/dist/{attachments-DNaSbmA3.d.ts → attachments-DAUy3vPR.d.ts} +3 -3
  26. package/dist/{attachments-DI710no4.d.cts → attachments-DaOaIBNf.d.cts} +7 -7
  27. package/dist/{bookingCopy-D2N3RhsV.d.ts → bookingCopy-CUW9Gdzc.d.ts} +15 -7
  28. package/dist/{bookingCopy-xlXxORCA.d.cts → bookingCopy-oEjqkiNA.d.cts} +15 -7
  29. package/dist/{branding-Bio5MM6g.d.cts → branding-aGleD_ki.d.ts} +99 -6
  30. package/dist/{branding-ytH4jJq1.d.ts → branding-ctD0B2Th.d.cts} +99 -6
  31. package/dist/{callRoom-7wrE3RP2.d.ts → callRoom-B1qq_D-4.d.ts} +1 -1
  32. package/dist/{callRoom-B_EqaUzx.d.cts → callRoom-DfZTWFXh.d.cts} +1 -1
  33. package/dist/{casesRepository-B-md8h1S.d.cts → casesRepository-Bzs7aw4t.d.cts} +2 -2
  34. package/dist/{casesRepository-CC6951TK.d.ts → casesRepository-CywqL6mj.d.ts} +2 -2
  35. package/dist/chat/index.cjs +335 -1
  36. package/dist/chat/index.d.cts +12 -12
  37. package/dist/chat/index.d.ts +12 -12
  38. package/dist/chat/index.js +335 -1
  39. package/dist/client/index.cjs +115 -11
  40. package/dist/client/index.d.cts +14 -14
  41. package/dist/client/index.d.ts +14 -14
  42. package/dist/client/index.js +115 -11
  43. package/dist/{config-BM0sv5jx.d.ts → config-BysLVhku.d.ts} +3 -3
  44. package/dist/{config-Daa1plhQ.d.cts → config-EZzEyS-c.d.cts} +3 -3
  45. package/dist/consent/index.cjs +112 -1
  46. package/dist/consent/index.d.cts +9 -9
  47. package/dist/consent/index.d.ts +9 -9
  48. package/dist/consent/index.js +112 -1
  49. package/dist/{context-DQHtwcHN.d.cts → context-DUwYArkM.d.cts} +9 -9
  50. package/dist/{context-CDV2pwGI.d.ts → context-DpfgbnOU.d.ts} +9 -9
  51. package/dist/{copy-BEx5F71o.d.cts → copy-DK4r9oGz.d.cts} +5 -5
  52. package/dist/{copy-Cl1l-qmY.d.ts → copy-LxZGnFXJ.d.ts} +5 -5
  53. package/dist/core/index.cjs +140 -13
  54. package/dist/core/index.d.cts +19 -19
  55. package/dist/core/index.d.ts +19 -19
  56. package/dist/core/index.js +140 -13
  57. package/dist/{depth-CAAwiStc.d.cts → depth-DAd77kRe.d.cts} +7 -6
  58. package/dist/{depth-DfNRVtrV.d.ts → depth-Mk7quUNa.d.ts} +7 -6
  59. package/dist/{entitlementRepository-CVWvND1P.d.ts → entitlementRepository-CrD0O7VR.d.ts} +4 -4
  60. package/dist/{entitlementRepository-DPvWjV-N.d.cts → entitlementRepository-Dra_4FU_.d.cts} +4 -4
  61. package/dist/{identity-DUlshgow.d.cts → identity-75whJXyP.d.cts} +2 -2
  62. package/dist/{identity-BhT7oJDS.d.ts → identity-BiyMDMrR.d.ts} +2 -2
  63. package/dist/index.cjs +365 -12
  64. package/dist/index.d.cts +19 -19
  65. package/dist/index.d.ts +19 -19
  66. package/dist/index.js +365 -12
  67. package/dist/{models-DSqA5Ahs.d.ts → models-CrbUwbuS.d.ts} +1 -1
  68. package/dist/{models-Bn2z0NNo.d.cts → models-DzvqEjMm.d.cts} +1 -1
  69. package/dist/notifications/index.cjs +94 -2
  70. package/dist/notifications/index.d.cts +5 -5
  71. package/dist/notifications/index.d.ts +5 -5
  72. package/dist/notifications/index.js +94 -2
  73. package/dist/{pets-Bneph9m1.d.cts → pets-DoXxTw4x.d.cts} +2 -2
  74. package/dist/{pets-DjcLmRrW.d.ts → pets-W2BuLYko.d.ts} +2 -2
  75. package/dist/{petsRepository-BwxmfX_s.d.ts → petsRepository-ByMzq1Z9.d.ts} +2 -2
  76. package/dist/{petsRepository-BnVre3q7.d.cts → petsRepository-yyoo0k2I.d.cts} +2 -2
  77. package/dist/photo/index.cjs +131 -1
  78. package/dist/photo/index.d.cts +3 -3
  79. package/dist/photo/index.d.ts +3 -3
  80. package/dist/photo/index.js +131 -1
  81. package/dist/{ports-BPKxFOSe.d.cts → ports-BeVJYP0_.d.cts} +7 -7
  82. package/dist/{ports-Di9rdLO9.d.ts → ports-zYl8S_lI.d.ts} +7 -7
  83. package/dist/records/depth/index.cjs +200 -2
  84. package/dist/records/depth/index.d.cts +11 -11
  85. package/dist/records/depth/index.d.ts +11 -11
  86. package/dist/records/depth/index.js +200 -2
  87. package/dist/records/index.cjs +742 -4
  88. package/dist/records/index.d.cts +22 -14
  89. package/dist/records/index.d.ts +22 -14
  90. package/dist/records/index.js +742 -4
  91. package/dist/{requestFunnel-CiSX2pun.d.ts → requestFunnel-BPeBZdKp.d.ts} +5 -5
  92. package/dist/{requestFunnel-DMR_1T3O.d.cts → requestFunnel-DHeSzCT6.d.cts} +5 -5
  93. package/dist/{runtimeTypes-CkMFCbHW.d.ts → runtimeTypes-Cc207nbn.d.ts} +7 -7
  94. package/dist/{runtimeTypes-2gJ5hYoE.d.cts → runtimeTypes-DgMizQKz.d.cts} +7 -7
  95. package/dist/server/events/index.cjs +92 -2
  96. package/dist/server/events/index.d.cts +4 -4
  97. package/dist/server/events/index.d.ts +4 -4
  98. package/dist/server/events/index.js +92 -2
  99. package/dist/server/index.cjs +95 -2
  100. package/dist/server/index.d.cts +5 -5
  101. package/dist/server/index.d.ts +5 -5
  102. package/dist/server/index.js +95 -2
  103. package/dist/televet/booking/index.cjs +396 -4
  104. package/dist/televet/booking/index.d.cts +24 -127
  105. package/dist/televet/booking/index.d.ts +24 -127
  106. package/dist/televet/booking/index.js +396 -4
  107. package/dist/televet/call/index.cjs +71 -1
  108. package/dist/televet/call/index.d.cts +3 -3
  109. package/dist/televet/call/index.d.ts +3 -3
  110. package/dist/televet/call/index.js +71 -1
  111. package/dist/televet/index.cjs +101 -1
  112. package/dist/televet/index.d.cts +25 -8
  113. package/dist/televet/index.d.ts +25 -8
  114. package/dist/televet/index.js +101 -1
  115. package/dist/{bookingTypes-D4pD3tHs.d.ts → televetGlobal-C5dTUACe.d.cts} +34 -1
  116. package/dist/{bookingTypes-D4pD3tHs.d.cts → televetGlobal-CnjrfhT4.d.ts} +34 -1
  117. package/dist/testing/index.cjs +98 -7
  118. package/dist/testing/index.d.cts +3 -3
  119. package/dist/testing/index.d.ts +3 -3
  120. package/dist/testing/index.js +98 -7
  121. package/dist/testing/rn/index.cjs +101 -1
  122. package/dist/testing/rn/index.d.cts +13 -13
  123. package/dist/testing/rn/index.d.ts +13 -13
  124. package/dist/testing/rn/index.js +101 -1
  125. package/dist/testing/web/index.cjs +95 -1
  126. package/dist/testing/web/index.d.cts +14 -14
  127. package/dist/testing/web/index.d.ts +14 -14
  128. package/dist/testing/web/index.js +95 -1
  129. package/dist/{timelineRows-D3171ksh.d.ts → timelineRows-D22vGN9t.d.ts} +1 -1
  130. package/dist/{timelineRows-DDnz4gx2.d.cts → timelineRows-DUQIWSjc.d.cts} +1 -1
  131. package/dist/{tokens-BYtbKwFh.d.cts → tokens-RAKZj7M8.d.cts} +89 -5
  132. package/dist/{tokens-BYtbKwFh.d.ts → tokens-RAKZj7M8.d.ts} +89 -5
  133. package/dist/{typeStyle-BqfwxZH4.d.cts → typeStyle-DVj8iCC1.d.ts} +30 -3
  134. package/dist/{typeStyle-DKz02DGU.d.ts → typeStyle-Nt7w0rhE.d.cts} +30 -3
  135. package/dist/{useRecordsDepth-Z9FF5X4E.d.ts → useRecordsDepth-C0Qfoghi.d.ts} +7 -6
  136. package/dist/{useRecordsDepth-COJ0wSYK.d.cts → useRecordsDepth-Mbysp548.d.cts} +7 -6
  137. package/dist/{useVetVisit-Ds8f1peV.d.cts → useVetVisit-BQX_aTJs.d.cts} +2 -2
  138. package/dist/{useVetVisit-BsjoDO-n.d.ts → useVetVisit-RPBOabl8.d.ts} +2 -2
  139. package/dist/video/index.cjs +122 -1
  140. package/dist/video/index.d.cts +3 -3
  141. package/dist/video/index.d.ts +3 -3
  142. package/dist/video/index.js +122 -1
  143. package/dist/{view-nRy6SitU.d.cts → view-CskIgOFV.d.cts} +5 -5
  144. package/dist/{view-DoQEHXJR.d.ts → view-DqKXtxEZ.d.ts} +5 -5
  145. package/dist/web/consent/index.cjs +111 -1
  146. package/dist/web/consent/index.d.cts +9 -9
  147. package/dist/web/consent/index.d.ts +9 -9
  148. package/dist/web/consent/index.js +111 -1
  149. package/dist/web/index.cjs +326 -14
  150. package/dist/web/index.d.cts +29 -23
  151. package/dist/web/index.d.ts +29 -23
  152. package/dist/web/index.js +326 -14
  153. package/dist/web/notifications/index.cjs +95 -2
  154. package/dist/web/notifications/index.d.cts +7 -7
  155. package/dist/web/notifications/index.d.ts +7 -7
  156. package/dist/web/notifications/index.js +95 -2
  157. package/dist/web/records/depth/index.cjs +170 -2
  158. package/dist/web/records/depth/index.d.cts +11 -11
  159. package/dist/web/records/depth/index.d.ts +11 -11
  160. package/dist/web/records/depth/index.js +170 -2
  161. package/dist/web/records/index.cjs +691 -4
  162. package/dist/web/records/index.d.cts +34 -37
  163. package/dist/web/records/index.d.ts +34 -37
  164. package/dist/web/records/index.js +691 -4
  165. package/dist/web/records/upload/index.cjs +3 -0
  166. package/dist/web/records/upload/index.d.cts +23 -0
  167. package/dist/web/records/upload/index.d.ts +23 -0
  168. package/dist/web/records/upload/index.js +3 -0
  169. package/dist/web/televet/booking/index.cjs +425 -4
  170. package/dist/web/televet/booking/index.d.cts +25 -39
  171. package/dist/web/televet/booking/index.d.ts +25 -39
  172. package/dist/web/televet/booking/index.js +425 -4
  173. package/dist/web/televet/call/index.cjs +28 -1
  174. package/dist/web/televet/call/index.d.cts +2 -2
  175. package/dist/web/televet/call/index.d.ts +2 -2
  176. package/dist/web/televet/call/index.js +28 -1
  177. package/dist/web/televet/index.cjs +102 -4
  178. package/dist/web/televet/index.d.cts +22 -11
  179. package/dist/web/televet/index.d.ts +22 -11
  180. package/dist/web/televet/index.js +102 -4
  181. package/package.json +20 -4
  182. package/web/records/upload/native-blocked.cjs +12 -0
  183. package/web/records/upload/package.json +8 -0
  184. package/dist/televetGlobal-CEk0_cW9.d.cts +0 -34
  185. package/dist/televetGlobal-Cs9Wbqsy.d.ts +0 -34
package/CHANGELOG.md CHANGED
@@ -10,6 +10,356 @@ changes that has not been earned by any production integration yet.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.9.0] - 2026-10-02
14
+
15
+ ### Changed
16
+
17
+ - **A logo is shown only when it is an Everfur brand asset.** `logo_url` / `logo_url_dark` (and the new
18
+ `assistant_avatar_url`) render only for an image uploaded to Everfur and served from `sdk.everfur.com/brand/`
19
+ (`hardenBrandAssetUrl`), whichever layer names it; any other https URL is now ignored and the surface shows its
20
+ default. The console already emitted only such URLs, so a console-branded tenant sees no change; a `theme` prop
21
+ naming another origin's logo stops showing it. Owner decision 2026-10-02 (upload-only images).
22
+
23
+ ### Added
24
+
25
+ - **More partner branding, Stripe-style (owner decision 2026-10-02).** Additive on the appearance contract
26
+ (`contract/appearance/appearance_v1.json`) and on branding wire schema 2; with none of it set, Everfur's default
27
+ look is unchanged (the consumer-parity pin holds).
28
+ - Status colours: `colorSuccess`, `colorWarning`, `colorInfo` (flat `success_color`, `warning_color`, `info_color`,
29
+ each with a `_dark` twin that is derived when unset, as `danger_color`). Success repaints positive status
30
+ (`Ready to view`, `Reviewed`, the in-range marks), warning the `Vet care suggested soon` banner and the caution
31
+ badges, info the `Good to know` and `Monitor at home` banners (which otherwise follow `colorPrimary`). Each is
32
+ guarded where it is drawn (text 4.5:1, edges 3:1, light and dark) and reported by `themeAdjustments`. New colour
33
+ tokens `info`, `badgeBgInfo`, `infoEdge`.
34
+ - Typography: `fontFamilyHeading` (`heading_font_family`), `fontWeightBody` and `fontWeightHeading`
35
+ (`font_weight_body`, `font_weight_heading`, 100 to 900 in hundreds) and `lineHeight` (`line_height`, 1.0 to 2.0,
36
+ scaling every line-height token). New type tokens `headingFamily`, `headingWeight`, `fontSources`; `headingFace()`
37
+ (root, `client`, `web`) and `headingFaceCss()` (`web`) pick a heading's family and weight, exactly `face()` /
38
+ `faceCss()` when nothing is set. `EverfurFontWeight` now names every hundred from 100 to 900.
39
+ - `appearance.fonts` (flat `font_sources`): up to four `{ family, src }` font files or `{ family, cssSrc }`
40
+ stylesheets, https only. The web provider loads each once per page before the first paint where it can (FontFace
41
+ API, or one `<link rel="stylesheet">`); a failure is silent for the member and writes one `[everfur]` warning in a
42
+ development build. In-page only: the hosted frame keeps Everfur's fonts and loads none of the list. React Native
43
+ never fetches a font: load it with expo-font and pass the family.
44
+ - Images: `logoUrl` (`logo_url`) now also shows in the records and booking headers, and `assistantAvatarUrl`
45
+ (`assistant_avatar_url`, new `meta.assistantAvatarUrl`) beside each assistant reply in the chat; removed on a load
46
+ error so the default shows. Both are upload-only (owner decision 2026-10-02): only an Everfur brand asset
47
+ (`https://sdk.everfur.com/brand/<16 hex>.png|jpg|webp`, what the console's upload returns) is ever fetched.
48
+ - Eight surface slots, the same four paint keys, wired on React Native and the web: `header`, `banner`, `chip`,
49
+ `card` (record cards too unless `recordCard` is set), `input`, `tab`, `bookingStep`, `dialog`. Emergency and
50
+ critical surfaces keep Everfur's locked danger paint. Each new field is lockable in `locked_fields`.
51
+
52
+ ## [0.8.0] - 2026-10-02
53
+
54
+ ### Added
55
+
56
+ - **`identitySignatureRequired` is a named error code.** The pets routes' 403 for a health field on a session your
57
+ backend did not sign (`registerPet` or `updatePet` with health fields, or `getPetProfile`, while anonymous)
58
+ settled as `unknown`. It is now `identitySignatureRequired` (category `authentication`, not retryable), shown to an
59
+ end user with the existing access-denied sentence. Documented in 11-ERRORS. (#155)
60
+ - **`EverfurError.retryAfterSeconds`.** The wait a refusal named, in seconds: the body's `retry_after_seconds`, or a
61
+ `Retry-After` header the transport folds into it. (#155)
62
+ - **`@everfur/sdk/web/records/upload`**: `createWebRecordsUploadTransport` on its own entry, for the `uploadTransport`
63
+ on a provider config at the app root (1_295 bytes minified, against 85_208 for the same import from
64
+ `@everfur/sdk/web/records`, and 388_582 before the tree-shaking change under Changed). `@everfur/sdk/web/records` still exports it. (#158)
65
+ - **`TelevetBookingController.releaseHold(slotId)`** (optional on the interface, so a host's own controller
66
+ still type-checks): `DELETE /widget/v1/televet/slots/{slot_id}/hold`, served by EFBackend from #1865. The
67
+ server releases only this member's own hold and answers the same either way. A server without the route (a
68
+ dark 404) settles `ok`, and the hold lapses as before. (#144)
69
+ - Colour tokens `highlight`, `onHighlight`, `mark`, `controlEdge`, `structureEdge` and `primaryEdge` on
70
+ `EverfurColorTokens`; `themeAdjustments` and `EverfurThemeAdjustment`; the `SlotSurrounds` type the slot guard now
71
+ takes; and `ThemeSeams.isDevelopment`. Additive: the appearance contract
72
+ (`contract/appearance/appearance_v1.json`) and the branding wire are unchanged. (#145)
73
+ - **`consentText`: the records consent gate shows the partner's own consent text (owner decision 2026-10-02).**
74
+ Every records surface that takes `consentVersion` now takes `consentText` beside it (React Native:
75
+ `EverfurRecordsFlow`, `EverfurRecordsDashboard`, `EverfurRecordDetail`, `EverfurRecordDocument`; web: the same
76
+ plus `EverfurRecordsPage`; the iframe embed: the `consentText` init option and `data-consent-text`, an additive
77
+ `ef:init` field). The gate renders it as plain text, line breaks kept, above `Allow`, which stays its own button.
78
+ The visible `DRAFT COPY` line that held its place is gone: Everfur supplies no consent wording, so a surface
79
+ given `consentVersion` without `consentText` shows only `Consent required` and `Allow`, and a development build
80
+ logs one `[everfur]` console warning naming the missing prop. Optional in the types (a minor addition), because
81
+ `consentVersion` is optional too. Documented in 09-GUIDE-RECORDS-AND-CONSENT, 05-SDK-INTEGRATION,
82
+ 13-WEB-INTEGRATION and the quickstart; the examples pass both. The Everfur CLI's docs mirror resyncs itself. (#168)
83
+
84
+ ### Changed
85
+
86
+ - **A partner's bundler can now tree-shake the web entries.** Importing one thing from `@everfur/sdk/web` used to
87
+ cost the whole entry: a page importing only `petRef` carried 354_011 minified bytes, exactly what `EverfurChat`
88
+ costs, because the build kept function names with a call after every function and its minifier deleted every
89
+ `/* @__PURE__ */` annotation. The published files are now minified without renaming anything and keep their pure
90
+ annotations, so a page that imports `petRef` alone carries 29_035 bytes (esbuild, minified), and the partner demo's
91
+ app root went from 686_156 to 203_196. Function and component names are unchanged in stack traces and React
92
+ DevTools. `tests/supply-chain/consumerTreeShake.test.ts` keeps it that way. (#158)
93
+
94
+ ### Fixed
95
+
96
+ #### Packaging and client
97
+
98
+ - **Importing `@everfur/sdk/web` from a Next.js server component works.** It failed with the cryptic
99
+ `(0, c.createContext) is not a function`: the provider and hooks ran in the server graph. Every `web/*` entry and
100
+ `testing/web` now opens with `"use client"` (`scripts/add-use-client.mjs`, run by `build`), as React libraries
101
+ mark their client entries; `./server`, `./client`, `./core` and the React Native entries do not. Server rendering
102
+ of the client components is unchanged (pinned by rendering the built provider with `renderToString`). (#163)
103
+ - **One session mint per page load, not three.** Every runtime's identity called your `getToken` for itself, so
104
+ React StrictMode's second runtime (and any second `EverfurProvider`) minted again in parallel: the production
105
+ integration test of 0.7.1 counted three `POST` session mints on every load. Concurrent mints for the same
106
+ tenant (publishable key and API base URL) and the same user now share one `getToken` call. Only the call in
107
+ flight is shared: the next mint after it settles calls `getToken` again, so the expiry your minter names and
108
+ a sign-out mean what they did. (#140)
109
+ - **A pet is registered once per burst, not once per caller.** A sample app counted every pet registered twice per
110
+ navigation: React StrictMode runs the chat's `pet` registration twice, and a host registering on load sends the
111
+ same body beside it. Concurrent identical `registerPet` calls (same tenant, identity, pet and body, from one
112
+ runtime or several, and from the chat's `pet` prop) now share one `POST /widget/v1/pets/{pet_ref}` and its
113
+ result, as session mints do (#140). Only the request in flight is shared; the next call sends again. (#160)
114
+ - **The birth date rule is documented.** The same report met `validationFailed` with no request id on
115
+ `birthDate: '2026-09-01'`. A recent date, today's included, is accepted (now pinned by a test); the refusal a
116
+ birth date meets before any request is the pair rule (a birth date always goes with `birthDatePrecision`), which
117
+ was documented nowhere. 09-GUIDE now states it and where the reason is (`error.wireMessage`). (#160)
118
+ - **The anonymous-session budget's 429 is told apart.** A new anonymous identity per page load (the session id is
119
+ memory-only) meets the API's budget of 20 new anonymous identities per IP per 5 minutes, and the 429 read as a
120
+ send throttle. The API now sends `Retry-After` and `reason_code: anonymous_session_limit` on it; the SDK settles
121
+ it as `rateLimited` with that `reasonCode` and `retryAfterSeconds`, and (through the header fold) does not retry
122
+ into a wait longer than its retry budget. Documented in 10-TESTING and 11-ERRORS. (#155)
123
+
124
+ #### Theme
125
+
126
+ - **Everfur's colours no longer leak through a partner palette (web and React Native).** A production integration
127
+ test of 0.7.1 themed through `appearance` (teal primary, warm white background) still showed Everfur's mint pet
128
+ avatar, the pastel green `Good to know` banner, a pink picked day chip and `Now` pill, and a green current step on
129
+ the request timeline. Each now paints from the theme: the picked chips, the `Now` pill, the open history row and a
130
+ pet's initials disc from the new `highlight` / `onHighlight` tokens (a tint of the partner's accent, else its
131
+ primary), the avatar ring and the current step from `mark`, and the `Good to know` and `Monitor at home` banners
132
+ from the brand's wash, outline and ink (`Vet care suggested soon` from the palette's caution tokens). Everfur's
133
+ own light look is unchanged: every new token is the compiled value it stands in for, and the banners and the
134
+ initials disc keep the app's authored paint there. (#145)
135
+ - **The dark palette is dark-aware everywhere (`night` preset).** The calm urgency banners were self-contained
136
+ pastel chips (THEME-01), a glaring mint or amber slab on the dark ground, and the picked chip and `Now` pill stayed
137
+ light pink. They now take the dark palette's tokens: a dark wash under light ink, and the pink's dusk twin
138
+ (`#605051`) as the dark `highlight`. (#145)
139
+ - **Non-text contrast (WCAG 1.4.11).** The text guard is joined by a guard on the outlines that show a control or a
140
+ structural mark: the new `controlEdge` (outlined pills, such as the chat header's Vet entry), `structureEdge` (the
141
+ steps still to come on a request timeline and its rail, an informational banner's edge) and `mark` reach 3:1 on a
142
+ partner palette, and `primaryEdge` edges the send disc, the owner's bubble and a done step when a pale primary does
143
+ not stand out from its ground. A slot with an opaque fill that melts into the ground around it (a white
144
+ `userBubble` on a white chat) is given a `borderColor` that reads at 3:1 there, and a partner's own `borderColor`
145
+ on such a slot is pushed until it does. Everfur's compiled palettes keep their authored hairlines. (#145)
146
+ - **The guard no longer changes a partner's colours in silence.** `themeAdjustments(input)` lists every partner
147
+ colour the contrast guard replaced (field, role, requested, painted), and the provider writes that list to the
148
+ console once in a development build (`__DEV__` on React Native, the bundler's `process.env.NODE_ENV` on the web). (#145)
149
+ - **A partner brand that already reads keeps its outline's hue (teal `#0F766E`).** The non-text guard pushed an
150
+ outline that did not read (the brand's 20% hairline) toward black or white, so a teal partner's outlined pills
151
+ (the chat header's Vet entry among them) were painted grey and a development build reported
152
+ `primary_color (control outline): #0F766E -> #7C8683`, although the teal reads at 3:1 on its warm white and on
153
+ white. When the brand itself reads at 3:1 on every control ground the outline is now strengthened toward the brand
154
+ (`#49968E` on `#FFFBF5`) and nothing is reported; a brand that does not read is pushed and reported as before. (#170)
155
+
156
+ #### Records
157
+
158
+ - **A refused signature upload no longer fails the clinic request (web and React Native).** A production sandbox
159
+ test found the signature upload's direct S3 POST refused from a partner's page (the prod bucket's CORS listed only
160
+ Everfur origins), so every web clinic request failed at signing. When that POST itself fails (a network or CORS
161
+ refusal, or S3 answering non-2xx) the built-in pad now sends the same PNG inline as `signature_png_base64`, which
162
+ the create API has always accepted. A refused initiate, an untrusted policy host, a failed complete and a cancel
163
+ still fail as before. Web: the pad gains `capture(controller, signal)`, which the built-in forms use;
164
+ `register` is unchanged and still strict. React Native: the multi-clinic composer keeps the inline PNG when its
165
+ registration's S3 POST fails. (#162)
166
+ - **The clinic-release consent screen no longer reads "I, , authorize ..." (web and React Native).** The served
167
+ authorization sentence is the owner's own signed scope, and with no name typed yet it rendered with a blank
168
+ where the name goes. It now waits for a name: nothing stands in for it while the field is empty, and it
169
+ appears as the name is typed. Found by the production integration test of 0.7.1. (#139)
170
+ - **"Please add your signature before submitting." no longer greets the consent screen (web).** #127 moved the
171
+ line on the in-place clinic request forms; the consent screen the picker opens still showed it as soon as a
172
+ clinic was chosen. It now appears only when the signature is the one thing keeping `Send request` closed. (#139)
173
+ - **The React Native multi-clinic composer scrolls, closes and sends (React Native).** On an iPhone 17 simulator
174
+ (Expo 57, 2026-10-01) `EverfurClinicRequestBatch` was about 1112 pt of unscrolled content on an 874 pt screen: once
175
+ a clinic was added, Send and Cancel were out of reach, there was no close control at the top, the Search control
176
+ looked like a second text field, and the keyboard could not be dismissed. It now has a header with the composer's
177
+ own `Cancel`, the form in a keyboard-avoiding ScrollView that dismisses the keyboard on a drag or a tap outside a
178
+ control, and `Send` pinned in a footer above the home indicator. The search is one field whose return key
179
+ searches; Search is an outline pill. No new strings. (#143)
180
+ - **React Native records draw their icons (React Native).** The records pictograms rendered as empty tinted discs
181
+ (the empty states, the upload screen) and squares (the dashboard's action rows). They now draw the consumer app's
182
+ Feather icons with Views, as the chat glyphs do: `file-text`, `list`, `inbox` and `refresh-cw` on the action rows;
183
+ `search`, `inbox`, `activity`, `clock`, `tool` and `wifi-off` per empty state; `upload-cloud` on the upload screen.
184
+ They are drawn in the brand ink and hidden from assistive technology. (#149)
185
+ - **The records footnote sentence shows once per screen (web and React Native).** On `Visits & records` inside
186
+ `EverfurRecordsPage` the production integration test of 0.7.1 saw "Everfur summarizes what your vet recorded.
187
+ It is informational, not diagnostic." three times: the server's disclaimer band (the same words), the record
188
+ screen's footnote and the page's footnote. A screen the page shows now draws no footnote of its own under the
189
+ page's, and a server disclaimer that says exactly what the footnote says is not drawn again. A server
190
+ disclaimer with any other words is still rendered verbatim. (#142)
191
+ - **The records consent gate says what `Allow` agrees to, and its button is reachable on React Native.** Both
192
+ production integration tests of 0.7.1 saw only "Consent required" and `Allow`. The gate now shows a line for
193
+ what the member consents to between them. **That line is a visible `DRAFT COPY [[...]]` placeholder**: no
194
+ approved consent text exists and the server serves none, so the wording is the owner's decision (documented in
195
+ 09-GUIDE-RECORDS-AND-CONSENT.md). On React Native the gate's container was `accessible`, which grouped it into
196
+ one element named "Consent required" and hid the `Allow` button from a screen reader; the ask is now visible
197
+ text announced on mount, and `Allow` is its own button. (#150)
198
+ - **Web records forms and screens speak to assistive technology (accessibility).** A records flow opened on a given
199
+ screen (`initialScreen`) now moves focus to that screen's heading on mount, as every later screen change already
200
+ did; before, the host control that opened it unmounted and focus fell to the page's `<body>`. The clinic email
201
+ errors on the consent screen, a failed request's recovery and the clinic search's add-by-email form are bound to
202
+ their fields (`aria-invalid` + `aria-describedby`). The clinic search says how many clinics it found through a
203
+ polite status region (a DRAFT COPY slot until the owner approves a sentence). The upload card's heading is an
204
+ `h3` under the screen's `h2` instead of skipping to `h4`. (#161)
205
+ - **The records share sheet's audience chips wrap (web).** At 390px the `Boarding` chip was clipped on the right. (#145)
206
+ - **Every records control says what it shows (web and React Native, WCAG 2.5.3 Label in Name).** Owner decision
207
+ 2026-10-02: a control's accessible name is its visible text, so a speech user can activate it by reading it. The
208
+ records screens and the chat history named about twenty controls in other words (`Choose a file` was spoken
209
+ "Choose records to upload", `I already have the records` "Upload records you already have", `Cancel request`
210
+ "Cancel this records request"); each now takes its visible text as its name, busy labels included. A control
211
+ repeated in a list keeps its visible name and gets its row as context: `Copy link` is described by its share row
212
+ (`aria-describedby` on the web, `accessibilityHint` on React Native) and each request row's `Ask about this
213
+ request` has the row's source as its hint. The clinic picker's add-by-email toggle states open or closed through
214
+ `aria-expanded` and `accessibilityState.expanded` instead of a second name. No visible copy changed. (#169)
215
+ - **The single-clinic consent form lets the keyboard go and keeps `Send request` above it (React Native).** Its
216
+ ScrollView and the clinic results dismiss the keyboard on a drag (interactive on iOS, on-drag on Android) and keep
217
+ taps on controls. The pinned `Send request` footer lifts by the keyboard measured from the avoiding view's own
218
+ top, as the multi-clinic composer's does (#143); before, it sat half under the keyboard. (#170)
219
+ - **The search glyph has its handle (React Native).** The clinic empty state's and the chat history's search glyph
220
+ drew its handle detached from the ring, and the clinic picker's search bar drew a bare ring. Both now draw
221
+ Feather's `search`: a ring with the handle growing out of it, down and to the right. (#170)
222
+
223
+ #### Chat
224
+
225
+ - **Chat shows the pet a host registers on load, without waiting for a send (web and React Native).** The pet
226
+ list behind the chat header, the empty-state title and the starter chips was read once on mount, so a host
227
+ that calls `registerPet` as the page loads (the documented shape) raced it: the production integration test of
228
+ 0.7.1 opened chat before the write landed and saw "What can we help your pet with today?", "Mixed Breed" and a
229
+ `?` avatar until the member sent a message. A successful `registerPet`, `updatePet` or `deletePet` now makes
230
+ `useEverfurPets` (and so chat) read the list again. (#141)
231
+ - **Chat conversations are saved under the chat's pet again (web and React Native).** Since 0.7.0 the first
232
+ send creates the conversation (#129), and it created it with the turn's pet, which `useEverfurChat().send`
233
+ never names: every conversation was created unscoped and saved with `pet_ref` null, so the pet's history
234
+ drawer read "No chats yet" right after a chat (mobile integration test of 0.7.1). A send that names no pet now
235
+ uses the chat's own pet scope; a send that names one keeps it, and an unscoped chat is unchanged. (#148)
236
+ - **React Native chat lets the keyboard go, and the urgency banner compacts while typing (React Native).** Once
237
+ open, the chat keyboard could not be dismissed, and the pinned urgency banner kept its full height while the
238
+ member typed (about 40% of what was left of the screen). The thread and the starter-prompt ScrollViews now
239
+ dismiss the keyboard on a drag (interactive on iOS, on-drag on Android). While the keyboard is open the banner is
240
+ compact: tighter padding and the reason held to two lines (a screen reader still reads it whole). The tier label,
241
+ the vet pill, poison control and the dismiss stay, so an emergency keeps its way to care. (#147)
242
+ - **The web chat holds up in forced-colors mode (Windows High Contrast) (accessibility).** The header's New chat and
243
+ History glyphs vanished, the starter chips and the Send disc lost their edges, and the brand-ink upload and mic
244
+ glyphs fell below contrast. Every chat glyph now draws in `currentColor` with its ink as the SVG's CSS `color`
245
+ (and `forced-color-adjust: auto`, since browsers otherwise preserve an SVG's own colour there), so the mode
246
+ repaints it in the system colours; the starter chips, the header's icon buttons, the composer's glyph controls
247
+ and the Send disc carry a 1px transparent edge that only the mode draws. The normal look is unchanged. (#159)
248
+ - **The starter chips follow a partner's palette and the dark palette (React Native).** Off Everfur's own light look
249
+ the four starter chips stayed Everfur's blue, green, amber and pink pastels, which glared on the night ground. They
250
+ now paint the theme's `highlight` under `onHighlight`. Everfur's own look keeps its pastels and black ink. (#170)
251
+ - **The starter chips follow a partner's palette and the dark palette (web).** The web chat's starter chips kept
252
+ Everfur's blue, green, amber and pink pastels under every partner preset and glared on the night ground. Off
253
+ Everfur's own light look they now paint the theme's `highlight` under `onHighlight` (4.5:1), the rule the React
254
+ Native chips follow since #170. Everfur's own look keeps its pastels and black ink. (#172)
255
+
256
+ #### Vet visits
257
+
258
+ - **The default vet visit can offer the pet's own clinic and your emergency-vet locator (web and React Native).**
259
+ `primaryVet` and `onFindEmergencyVet` reached the emergency warning only through `EverfurTelevet`; the in-app
260
+ visit Everfur provides by default (`EverfurTelevetHost`) rendered it without them, so the warning offered ASPCA
261
+ Animal Poison Control and no way to the member's clinic. The web host now passes both through, as it does
262
+ `onPickMedia`. The React Native host took no props at all; it now takes the same four (`onAddToCalendar`,
263
+ `onPickMedia`, `primaryVet`, `onFindEmergencyVet`), and `@everfur/sdk/televet` exports their types. (#157)
264
+ - **The vet booking asks each question once (web and React Native).** Every booking read kept its own state, so the
265
+ dialog asked its eight or nine questions again on every open, the state policy twice per open (the flow and its
266
+ `where` step), and ten open/close cycles ran a member into the partner plane's rate limit (2026-10-01 audit). The
267
+ read-only reads (the roster, the slots, the soonest opening, the state policy, the cancellation terms, the visits)
268
+ now share one answer per question per signed-in session: a question already in flight is joined, a settled answer
269
+ is reused for 30 seconds (the visits list a member comes back to is still read again), and a hold, booking,
270
+ move, cancel or released hold drops the answers it changed. The entitlement read is joined but never reused. A
271
+ rate-limited answer is kept for the server's own wait, so a reopened dialog shows the rate-limit state it already
272
+ has instead of spending another request. A 429 or 503 whose wait rides only in the `Retry-After` header is now
273
+ honoured like one whose body names it. (#166)
274
+ - **A test tenant's vet entry no longer reads the real vet roster or slots.** The booking container prefetched the
275
+ real roster, bios, photos and bookable slots even when it then drew the visit simulator. On a test tenant those
276
+ reads now wait for the allowance answer and never start when the simulator applies. A live tenant reads them at
277
+ once, as before; a test tenant with an allowance still books real visits. (#162)
278
+ - **A rate-limited booking read says so, and offers "Try again" when it can work (web and React Native).** After
279
+ about eight booking opens in eight minutes the production integration test of 0.7.1 got 429 on the vet roster
280
+ and the state policy, and the step showed only "Could not load" with a "Try again" that could not succeed.
281
+ The pet, vet and time steps now show the SDK's existing rate-limit state ("Slow down a moment" / "You’ve made
282
+ a lot of requests just now. You can try again shortly.", the records empty state's words) and offer "Try
283
+ again" once the server's `retry_after_seconds` has passed (60 seconds when it names none). Any other failure
284
+ shows the step's own card, as before. The steps take an optional `error` prop for this. (#146)
285
+ - **An unbooked vet visit hold is given back (web and React Native).** Reaching the booking review takes a
286
+ ten-minute hold, and nothing released it: closing the dialog left the time off every grid until the hold ran
287
+ out, and the same member coming back for it read "That time was just taken." about their own hold (production
288
+ integration test of 0.7.1). The booking flow now releases its hold when it closes or unmounts without booking,
289
+ and when another time replaces it. Back from review still keeps the hold, so the same time picked again goes
290
+ straight back to review on it. A hold the flow booked, or is booking, is never released. (#144)
291
+ - **No fee line on a partner's cancel receipt (web).** A partner member owes no cancellation fee (owner decision
292
+ 2026-10-01: a late cancel is used from the partner's allowance, and the server reports `fee_recorded: false` on
293
+ every partner cancel), yet the visits screen showed "No late cancellation fee was recorded for this visit." on
294
+ every receipt. That line now renders only when the server reports a recorded fee, so a fee that was recorded is
295
+ still named. No copy changed. (#153)
296
+ - **React Native booking follows the palette.** The progress bar (the booking chrome's done segments) and the
297
+ booked and joined rings now take `mark` instead of Everfur's green, the picked day `highlight` instead of the pink,
298
+ the primary button's edge `primaryEdge` (a pale partner primary, the disabled `Continue` included, keeps its
299
+ shape), and the full-screen booking modal sets the status bar to light content over a dark ground (the `night`
300
+ preset left the iOS clock dark on dark). (#145)
301
+ - **The booking emergency panel is as tall as its content (web and React Native).** The early emergency step grew
302
+ to the dialog's height, a tall red box with nothing in its lower half. (#145)
303
+ - **The vet visit dialog states `aria-modal` (web).** It is a `<dialog>` shown with `showModal()`, so the browser
304
+ already treats it as modal and the page behind it as inert; it now says so in the attribute too, as the records
305
+ dialogs (the share sheet's among them) do, for tools that read attributes and for a DOM without `showModal`. (#164)
306
+ - **The web booking's choices and times are walked with the arrow keys (accessibility).** The option cards (pet,
307
+ where, the emergency check, a cancellation's reason) and the time grid made every choice its own tab stop. Each
308
+ radio group is now one tab stop whose arrows move the pick and wrap, as native radios do; the time grid is one tab
309
+ stop walked with Left, Right, Up, Down, Home and End, where focus never holds a slot (Enter or a click does), and
310
+ a chip stays focusable while its hold is in flight. The review summary is a valid description list again (its
311
+ "Change" sits inside the value it changes), and the visit dialog is named by the heading on screen rather than
312
+ by a title no screen shows. (#156)
313
+ - **The web booking's emergency warning comes into view, and a busy button keeps the focus (accessibility).** At
314
+ 200% zoom (640x450) "Yes, at least one" opened the emergency warning below the step's scroll area: it was
315
+ announced, but a sighted member saw only a Continue that had gone dark. The warning is now scrolled into view
316
+ (`block: 'nearest'`, instant under reduced motion), and the React Native step scrolls to it the same way. A booking
317
+ button that went busy or blocked used the native `disabled` attribute, which drops focus to the partner page's
318
+ `<body>` mid-booking; it is now `aria-disabled`, stays focusable, swallows the press, points `aria-describedby` at
319
+ the reason where the screen shows one, and speaks the deck's own busy words ("Booking…", "Moving…") through a
320
+ polite status region. The step's scroller now contains the review's visually hidden consent inputs, which had
321
+ stretched the visit dialog's own scroll height and scrolled the whole dialog when one took focus. (#154)
322
+
323
+ ### Documentation
324
+
325
+ - **The docs say what the UI and the wire do (production sandbox and Next.js integration tests, 2026-10-01).**
326
+ The vet booking is eight steps with a pet chooser and seven for a member with one pet, not always eight. The chat
327
+ stream's `metadata` frame arrives BEFORE `done`, which repeats its title and follow-up questions; 10-TESTING
328
+ said a frame arrives after `done` and the quickstart said a later `metadata` frame can follow. The examples
329
+ script no longer says a sandbox uses an ordinary `pk_live_` key on staging, and the onboarding page no longer says
330
+ most sandboxes come from self-serve sign-up, which is not open to partners yet. (#165)
331
+ - **Current facts in the README and the vet visit guide.** The README named `0.3.0` and called vet visits "not
332
+ yet enabled", and 16-TELEVET-VISITS.md opened with "draft ... not yet enabled for any partner"; vet visits run
333
+ in production for the partners Everfur enables them for. The README quickstart's `getToken` used a relative
334
+ URL, which does not resolve in a React Native bundle (00-QUICKSTART.md already said so). The camera note said
335
+ to add vision-camera's config plugin: `4.x` has one, `5.x` has none and needs `react-native-nitro-modules` and
336
+ `react-native-nitro-image`. The React Native call setup now names all of Daily's declared peers
337
+ (`@react-native-async-storage/async-storage`, `react-native-background-timer`, `react-native-get-random-values`
338
+ beside the WebRTC build) and the config plugin's one-Expo-SDK peer, with the `--legacy-peer-deps` install and
339
+ the `overrides` entry that keeps the next install and `npm ci` passing. `registerEverfurDaily`'s doc comment on
340
+ React Native now imports it from `@everfur/sdk/televet/call`, where it is exported. Found by the 2026-10-01
341
+ production integration tests of 0.7.1 (web and mobile). (#151)
342
+ - **Test mode is a production sandbox with a `pk_test_` key.** 00-QUICKSTART, 10-TESTING, 12-CLI-AND-MCP and the
343
+ README describe test mode as a sandbox tenant with a `pk_test_` key on the production host (an older sandbox may
344
+ still carry `pk_live_`; staging is early access), the go-live pair, the console's live/test switch, the hosted MCP
345
+ reconnect and the test/staging/production plan environments; 02, 08, 11 and 14 no longer say that no deployment
346
+ accepts `pk_test_`. The operator guide (04-ONBOARDING-A-CUSTOMER) covers going live by pairing. Every heading and
347
+ anchor is unchanged. (#101)
348
+
349
+ ### Tests and tooling
350
+
351
+ - **The records bundle budget is re-measured.** The 2026-10-01 records fixes (#139, #142, #143, #149) each passed
352
+ on their own head and together crossed the React Native records closure's line by 349 gzip bytes, all records
353
+ feature code; the closure still holds no chat, vet visit, video, photo or animations module. The line is the CI
354
+ measurement plus 5%, as every budget entry is. (#152)
355
+ - **Chat's first reads are pinned beside the entitlement verdict.** A test holds the verdict unanswered over the
356
+ real transport and asserts the conversation head, the suggested prompts and the pets requests are already out, so
357
+ a later gate on the verdict cannot add that hop. (#167)
358
+ - **The consent gate's `Allow` is pinned reachable on the dashboard, and the theme guard's development warning is
359
+ pinned to a runtime switch of appearance.** No ancestor of `Allow` on the React Native dashboard is `accessible`;
360
+ switching to another adjusted preset at runtime writes that preset's line once, and a look the guard leaves alone
361
+ writes nothing. (#170)
362
+
13
363
  ## [0.7.1] - 2026-10-01
14
364
 
15
365
  ### Fixed
package/README.md CHANGED
@@ -10,12 +10,15 @@ features without shipping an app update.
10
10
  a blank screen, and cannot be force-enabled client-side.
11
11
  - The app never holds a secret. See [The security model](#the-security-model).
12
12
 
13
- > `0.3.0`, MIT licensed, public on npm. Requires React Native `>=0.74` (or React `>=18` with `react-dom`
13
+ > `0.9.0`, MIT licensed, public on npm. Requires React Native `>=0.74` (or React `>=18` with `react-dom`
14
14
  > for the web entry), and Node `>=18` on the backend that mints sessions.
15
15
 
16
- **Getting keys:** Everfur keys are provisioned by the Everfur team during partner onboarding; there is
17
- no self-serve signup yet. Contact [support@everfur.com](mailto:support@everfur.com) to get set up. The
18
- publishable key is designed to ship inside your app; the secret key is not.
16
+ <!-- DRAFT COPY slot PD1-2 (owner): the README's invitation to get started (self-serve sandbox sign-up), in the owner's words. The factual paragraph below stands without it. -->
17
+ **Getting keys:** a sandbox (test-mode) tenant can be created from the partner console's sandbox page
18
+ (`https://everfur.com/partners/console/sandbox`) where Everfur has opened sandbox sign-up; otherwise the
19
+ Everfur team creates it. Live tenants and their keys are provisioned by the Everfur team. Contact
20
+ [support@everfur.com](mailto:support@everfur.com) to get set up. The publishable key is designed to ship
21
+ inside your app; the secret key is not.
19
22
 
20
23
  ## Install
21
24
 
@@ -36,8 +39,9 @@ Peer dependencies by capability (install only what you use):
36
39
 
37
40
  The camera peer is optional: if you never mount `./video` or `./photo`, you do not need it.
38
41
  `EverfurRecords` accepts the host's picker and upload transport, so use the native modules that fit your
39
- app (including Expo SDK 57) without an SDK peer-dependency constraint. `react-native-vision-camera` also
40
- needs its config plugin in `app.json` and a camera usage description.
42
+ app (including Expo SDK 57) without an SDK peer-dependency constraint. `react-native-vision-camera` needs a
43
+ camera usage description; `4.x` also ships an Expo config plugin (add it to `app.json` plugins), while `5.x`
44
+ ships none and instead needs its own peers `react-native-nitro-modules` and `react-native-nitro-image`.
41
45
 
42
46
  ### Bundler resolution
43
47
 
@@ -70,8 +74,8 @@ Two ways, one wire contract, documented in
70
74
  is [`examples/frame-embed.html`](https://sdk.everfur.com/v1/docs/index.html#web--the-snippet).
71
75
 
72
76
  Chat and records are the web surfaces (records on its own subpath and its own frame document, available
73
- when Everfur enables records for your account); photo and video remain React Native for now. Vet visits (not yet enabled)
74
- have their own entry on both hosts, `@everfur/sdk/web/televet` and `@everfur/sdk/televet`, and a visit opens
77
+ when Everfur enables records for your account); photo and video remain React Native for now. Vet visits (available when Everfur enables them for your
78
+ account) have their own entry on both hosts, `@everfur/sdk/web/televet` and `@everfur/sdk/televet`, and a visit opens
75
79
  INSIDE your app by default: booking, the confirmation and the call run in an Everfur modal (React Native) or
76
80
  dialog (web) that `VetVisitButton` and `EverfurChat` mount themselves; the hosted page is an explicit opt-in,
77
81
  `prefer="redirect"`. The booking screens (`@everfur/sdk/televet/booking`, `@everfur/sdk/web/televet/booking`)
@@ -93,13 +97,14 @@ export default function App() {
93
97
  return (
94
98
  <EverfurProvider
95
99
  config={{
96
- publishableKey: process.env.EXPO_PUBLIC_EVERFUR_PUBLISHABLE_KEY!, // pk_live_... safe to ship
100
+ publishableKey: process.env.EXPO_PUBLIC_EVERFUR_PUBLISHABLE_KEY!, // pk_test_... or pk_live_... safe to ship
97
101
  apiBaseUrl: 'https://api.everfur.com/api/v1',
98
102
  }}
99
103
  // Omit `user` entirely for anonymous mode (publishable key alone, general answers).
100
104
  user={{
101
105
  userRef: userRef('your-stable-user-id'),
102
- getToken: () => fetch('/everfur-session').then((r) => r.text()),
106
+ // An absolute URL: a relative path does not resolve in a React Native bundle.
107
+ getToken: () => fetch('https://your-backend.example.com/everfur/session', { method: 'POST' }).then((r) => r.text()),
103
108
  }}
104
109
  activePet={petRef('pet-ref-123')}
105
110
  >
@@ -140,23 +145,25 @@ import { petRef } from '@everfur/sdk';
140
145
  ## The security model
141
146
 
142
147
  The one rule: **the app never holds a secret.** Two provisioning credentials, kept apart, plus a read-only
143
- docs key for tooling. Test mode is a sandbox tenant on the staging API with an ordinary `pk_live_` key
144
- (`livemode: false` in its entitlements); no deployed environment accepts a `pk_test_` key.
148
+ docs key for tooling. Test mode is a sandbox tenant with a `pk_test_` key on the production API
149
+ (`livemode: false` in its entitlements); the API accepts a `pk_test_` key only for a sandbox tenant, so a test
150
+ key never reaches live data. An older sandbox may still carry a `pk_live_` key, which keeps working, so read
151
+ `livemode`, not the prefix. Staging (`https://api-staging.everfur.com/api/v1`) is for early access.
145
152
 
146
153
  | Credential | Where it lives | What it does |
147
154
  | --- | --- | --- |
148
- | Publishable key `pk_live_...` | In the app (`EXPO_PUBLIC_...`). Safe to ship. | Names your tenant. Cannot mint a session or grant a feature by itself. |
155
+ | Publishable key `pk_test_...` (sandbox) or `pk_live_...` (live) | In the app (`EXPO_PUBLIC_...`). Safe to ship. | Names your tenant. Cannot mint a session or grant a feature by itself. |
149
156
  | Partner secret key `sk_partner_...` | **Only** on your server. Never in the app, its bundle, env, git, or logs. | Mints session tokens. Revocable and rotatable without an app release. |
150
157
  | Session token (bearer) | App memory, short-lived. | Authenticates one end user; re-minted via your backend on a 401. Signed, holds no secret, 15 minutes by default (1 minute to 1 hour). |
151
158
  | Docs key `dk_partner_...` | Only in the MCP server or CLI configuration on a developer machine. | Read-only: fetches the Everfur contract from `GET /partners/contract` and opens no other route. |
152
159
 
153
160
  Your backend exchanges the two provisioning credentials for a short-lived session by calling
154
- `POST /widget/v1/sessions` with `x-everfur-partner-key: pk_live_...` + `x-everfur-partner-secret-key:
155
- sk_partner_...`. The SDK ships a **server-only** helper (`mintPartnerSession`, imported from
161
+ `POST /widget/v1/sessions` with `x-everfur-partner-key: pk_...` + `x-everfur-partner-secret-key:
162
+ sk_partner_...`, both from the same tenant. The SDK ships a **server-only** helper (`mintPartnerSession`, imported from
156
163
  `@everfur/sdk/server`) so you do not hand-roll that request. That helper is deliberately absent from every
157
164
  React-Native-resolvable bundle: a device can never import it, so the secret has no path onto the phone.
158
165
 
159
- **Do:** ship `pk_live_` in the app; keep `sk_partner_` in a server secret manager; mint sessions on your
166
+ **Do:** ship the publishable key (`pk_test_` or `pk_live_`) in the app; keep `sk_partner_` in a server secret manager; mint sessions on your
160
167
  backend; serve everything over HTTPS.
161
168
  **Never:** put `sk_partner_` in the app; mint sessions on the device; try to force-enable a feature
162
169
  client-side (the server re-checks and returns `403`).
@@ -185,6 +192,7 @@ off-state, never a blank region.
185
192
  | `@everfur/sdk/animations` | server-driven pet-state media surface |
186
193
  | `@everfur/sdk/web` | the browser entry: `EverfurProvider`, `CapabilityGate`, `EverfurChat`, `useEverfurChat`, the theme hook, rendered with `react-dom`; withheld from React Native bundles the way `./server` is |
187
194
  | `@everfur/sdk/web/records` | records for a browser host: `EverfurRecordsPage` (the Everfur web app's records page, complete on its own: request from a clinic, upload, request status, the full record with vaccinations, all inside your page), the named screens, `EverfurRecords` (the React Native-shaped flow), `useEverfurRecords`, `createWebRecordsUploadTransport`, `fileToHandle`; its own entry, withheld from React Native like `./web`; available when Everfur enables records for the account. Frame mode serves it with `surface: 'records'` (see [chapter 13](https://sdk.everfur.com/v1/docs/index.html#web--2-frame-mode-the-loader-and-the-frame)) |
195
+ | `@everfur/sdk/web/records/upload` | `createWebRecordsUploadTransport` alone, for the `uploadTransport` on the provider config at your app root; the same function `@everfur/sdk/web/records` exports, without the records entry |
188
196
  | `@everfur/sdk/server` | **server-only** `mintPartnerSession` (no React Native resolution; never bundled on-device) |
189
197
  | `@everfur/sdk/server/events` | **server-only, Node only** partner events (preview): `constructEvent` to verify webhooks, `sendPartnerEvent`; see [chapter 15](https://sdk.everfur.com/v1/docs/index.html#sec-events) |
190
198
  | `@everfur/sdk/testing`, `@everfur/sdk/testing/rn`, `@everfur/sdk/testing/web` | test seams (`MockTransport`, `useEverfurTestEntitlements`, ...) for your own tests |
@@ -2,9 +2,9 @@ import { ReactNode } from 'react';
2
2
  import { StyleProp, ViewStyle } from 'react-native';
3
3
  import { P as PetRef, C as ConversationId } from './ids-B2GAAifq.cjs';
4
4
  import { F as FileHandle } from './ports-BN6RHF9W.cjs';
5
- import { a as ChatCheckinTarget } from './ports-BPKxFOSe.cjs';
6
- import { W as WidgetPet, P as PetProfileInput } from './petsRepository-BnVre3q7.cjs';
7
- import { U as UrgencyDisplayLevel, L as LifecyclePort, E as EverfurChatDraftStore } from './attachments-DI710no4.cjs';
5
+ import { a as ChatCheckinTarget } from './ports-BeVJYP0_.cjs';
6
+ import { W as WidgetPet, P as PetProfileInput } from './petsRepository-yyoo0k2I.cjs';
7
+ import { U as UrgencyDisplayLevel, L as LifecyclePort, E as EverfurChatDraftStore } from './attachments-DaOaIBNf.cjs';
8
8
 
9
9
  /** Where a picked image comes from: the host's photo library, or its camera. */
10
10
  type AttachmentSource = 'photo' | 'camera';
@@ -2,9 +2,9 @@ import { ReactNode } from 'react';
2
2
  import { StyleProp, ViewStyle } from 'react-native';
3
3
  import { P as PetRef, C as ConversationId } from './ids-B2GAAifq.js';
4
4
  import { F as FileHandle } from './ports-BN6RHF9W.js';
5
- import { a as ChatCheckinTarget } from './ports-Di9rdLO9.js';
6
- import { W as WidgetPet, P as PetProfileInput } from './petsRepository-BwxmfX_s.js';
7
- import { U as UrgencyDisplayLevel, L as LifecyclePort, E as EverfurChatDraftStore } from './attachments-SF88N654.js';
5
+ import { a as ChatCheckinTarget } from './ports-zYl8S_lI.js';
6
+ import { W as WidgetPet, P as PetProfileInput } from './petsRepository-ByMzq1Z9.js';
7
+ import { U as UrgencyDisplayLevel, L as LifecyclePort, E as EverfurChatDraftStore } from './attachments-CC7aHpwZ.js';
8
8
 
9
9
  /** Where a picked image comes from: the host's photo library, or its camera. */
10
10
  type AttachmentSource = 'photo' | 'camera';
@@ -1,5 +1,5 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
- import { f as RecordsScreenBaseProps } from './useRecordsDepth-COJ0wSYK.cjs';
2
+ import { f as RecordsScreenBaseProps } from './useRecordsDepth-C0Qfoghi.js';
3
3
 
4
4
  interface EverfurRecordsDepthProps extends Pick<RecordsScreenBaseProps, 'petRef' | 'petName' | 'onNavigate' | 'onBack'> {
5
5
  readonly style?: CSSProperties;
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { StyleProp, ViewStyle } from 'react-native';
3
- import { f as RecordsScreenBaseProps } from './useRecordsDepth-COJ0wSYK.cjs';
3
+ import { f as RecordsScreenBaseProps } from './useRecordsDepth-C0Qfoghi.js';
4
4
 
5
5
  interface EverfurRecordsDepthProps extends Pick<RecordsScreenBaseProps, 'petRef' | 'petName' | 'onNavigate' | 'onBack'> {
6
6
  readonly style?: StyleProp<ViewStyle>;
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { StyleProp, ViewStyle } from 'react-native';
3
- import { f as RecordsScreenBaseProps } from './useRecordsDepth-Z9FF5X4E.js';
3
+ import { f as RecordsScreenBaseProps } from './useRecordsDepth-Mbysp548.cjs';
4
4
 
5
5
  interface EverfurRecordsDepthProps extends Pick<RecordsScreenBaseProps, 'petRef' | 'petName' | 'onNavigate' | 'onBack'> {
6
6
  readonly style?: StyleProp<ViewStyle>;
@@ -1,5 +1,5 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
- import { f as RecordsScreenBaseProps } from './useRecordsDepth-Z9FF5X4E.js';
2
+ import { f as RecordsScreenBaseProps } from './useRecordsDepth-Mbysp548.cjs';
3
3
 
4
4
  interface EverfurRecordsDepthProps extends Pick<RecordsScreenBaseProps, 'petRef' | 'petName' | 'onNavigate' | 'onBack'> {
5
5
  readonly style?: CSSProperties;
@@ -1,4 +1,4 @@
1
- import { b as EverfurErrorCode, E as EverfurError } from './EverfurError-DmELDpXp.js';
1
+ import { b as EverfurErrorCode, E as EverfurError } from './EverfurError-pEXDbUJ1.js';
2
2
 
3
3
  /**
4
4
  * Recovery policy. A host may narrow retry ceilings (e.g. a metered plan) but can never widen the SDK's
@@ -1,4 +1,4 @@
1
- import { b as EverfurErrorCode, E as EverfurError } from './EverfurError-DmELDpXp.cjs';
1
+ import { b as EverfurErrorCode, E as EverfurError } from './EverfurError-pEXDbUJ1.cjs';
2
2
 
3
3
  /**
4
4
  * Recovery policy. A host may narrow retry ceilings (e.g. a metered plan) but can never widen the SDK's
@@ -1,5 +1,5 @@
1
1
  /** The frozen error taxonomy. Additive-only under the dated-contract policy. */
2
- type EverfurErrorCode = 'authExpired' | 'authRejected' | 'sessionRequired' | 'accessDenied' | 'paymentRequired' | 'quotaExceeded' | 'rateLimited' | 'validationFailed' | 'sdkUpdateRequired' | 'conflict' | 'idempotencyConflict' | 'idempotencyInFlight' | 'conversationNotFound' | 'petNotFound' | 'resourceNotFound' | 'streamTimeout' | 'mediaUploadFailed' | 'moderationBlocked' | 'upstreamLlmTimeout' | 'internalError' | 'notImplemented' | 'serviceUnavailable' | 'streamIncomplete' | 'unknown';
2
+ type EverfurErrorCode = 'authExpired' | 'authRejected' | 'sessionRequired' | 'accessDenied' | 'paymentRequired' | 'quotaExceeded' | 'rateLimited' | 'validationFailed' | 'sdkUpdateRequired' | 'conflict' | 'idempotencyConflict' | 'idempotencyInFlight' | 'conversationNotFound' | 'petNotFound' | 'resourceNotFound' | 'streamTimeout' | 'mediaUploadFailed' | 'moderationBlocked' | 'upstreamLlmTimeout' | 'internalError' | 'notImplemented' | 'serviceUnavailable' | 'streamIncomplete' | 'identitySignatureRequired' | 'unknown';
3
3
  /** Every code, frozen, for iteration in conformance / property tests. */
4
4
  declare const EVERFUR_ERROR_CODES: readonly EverfurErrorCode[];
5
5
  /**
@@ -137,6 +137,12 @@ interface EverfurError {
137
137
  * snake_case token is kept; anything else is dropped rather than carried.
138
138
  */
139
139
  readonly reasonCode?: string;
140
+ /**
141
+ * The wait the server named, in seconds, when it named one: the body's `retry_after_seconds`, or a `Retry-After`
142
+ * header on a 429 or 503. The anonymous-session budget's 429 (`reasonCode` `anonymous_session_limit`) carries
143
+ * it; the SDK does not retry past it.
144
+ */
145
+ readonly retryAfterSeconds?: number;
140
146
  /** raw wire text; telemetry / logs only, NEVER rendered. NON-ENUMERABLE. */
141
147
  readonly wireMessage?: string;
142
148
  /** Opaque parsed wire payload for debugging (untrusted, unvalidated). NON-ENUMERABLE. */
@@ -169,6 +175,8 @@ interface MakeEverfurErrorOptions {
169
175
  readonly reasonCode?: string | null;
170
176
  /** Opaque parsed wire payload. Carried NON-ENUMERABLY for debugging; never rendered. */
171
177
  readonly raw?: unknown;
178
+ /** The wait the response named, in seconds. Kept only when finite and not negative. */
179
+ readonly retryAfterSeconds?: number | null;
172
180
  }
173
181
  declare function makeEverfurError(code: EverfurErrorCode, opts?: MakeEverfurErrorOptions): EverfurError;
174
182
 
@@ -1,5 +1,5 @@
1
1
  /** The frozen error taxonomy. Additive-only under the dated-contract policy. */
2
- type EverfurErrorCode = 'authExpired' | 'authRejected' | 'sessionRequired' | 'accessDenied' | 'paymentRequired' | 'quotaExceeded' | 'rateLimited' | 'validationFailed' | 'sdkUpdateRequired' | 'conflict' | 'idempotencyConflict' | 'idempotencyInFlight' | 'conversationNotFound' | 'petNotFound' | 'resourceNotFound' | 'streamTimeout' | 'mediaUploadFailed' | 'moderationBlocked' | 'upstreamLlmTimeout' | 'internalError' | 'notImplemented' | 'serviceUnavailable' | 'streamIncomplete' | 'unknown';
2
+ type EverfurErrorCode = 'authExpired' | 'authRejected' | 'sessionRequired' | 'accessDenied' | 'paymentRequired' | 'quotaExceeded' | 'rateLimited' | 'validationFailed' | 'sdkUpdateRequired' | 'conflict' | 'idempotencyConflict' | 'idempotencyInFlight' | 'conversationNotFound' | 'petNotFound' | 'resourceNotFound' | 'streamTimeout' | 'mediaUploadFailed' | 'moderationBlocked' | 'upstreamLlmTimeout' | 'internalError' | 'notImplemented' | 'serviceUnavailable' | 'streamIncomplete' | 'identitySignatureRequired' | 'unknown';
3
3
  /** Every code, frozen, for iteration in conformance / property tests. */
4
4
  declare const EVERFUR_ERROR_CODES: readonly EverfurErrorCode[];
5
5
  /**
@@ -137,6 +137,12 @@ interface EverfurError {
137
137
  * snake_case token is kept; anything else is dropped rather than carried.
138
138
  */
139
139
  readonly reasonCode?: string;
140
+ /**
141
+ * The wait the server named, in seconds, when it named one: the body's `retry_after_seconds`, or a `Retry-After`
142
+ * header on a 429 or 503. The anonymous-session budget's 429 (`reasonCode` `anonymous_session_limit`) carries
143
+ * it; the SDK does not retry past it.
144
+ */
145
+ readonly retryAfterSeconds?: number;
140
146
  /** raw wire text; telemetry / logs only, NEVER rendered. NON-ENUMERABLE. */
141
147
  readonly wireMessage?: string;
142
148
  /** Opaque parsed wire payload for debugging (untrusted, unvalidated). NON-ENUMERABLE. */
@@ -169,6 +175,8 @@ interface MakeEverfurErrorOptions {
169
175
  readonly reasonCode?: string | null;
170
176
  /** Opaque parsed wire payload. Carried NON-ENUMERABLY for debugging; never rendered. */
171
177
  readonly raw?: unknown;
178
+ /** The wait the response named, in seconds. Kept only when finite and not negative. */
179
+ readonly retryAfterSeconds?: number | null;
172
180
  }
173
181
  declare function makeEverfurError(code: EverfurErrorCode, opts?: MakeEverfurErrorOptions): EverfurError;
174
182
 
@@ -1,4 +1,4 @@
1
- import { E as EverfurError } from './EverfurError-DmELDpXp.cjs';
1
+ import { E as EverfurError } from './EverfurError-pEXDbUJ1.cjs';
2
2
 
3
3
  /**
4
4
  * The settle type. An async public operation returns this instead of throwing for an EXPECTED failure.