@everfur/sdk 0.5.0 → 0.5.2

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 (154) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/dist/BookedScreen-BJPbXLCG.d.ts +54 -0
  3. package/dist/BookedScreen-Cnld-jB_.d.cts +54 -0
  4. package/dist/{Chat-TA-aTcVF.d.cts → Chat-B43wIXCv.d.cts} +3 -3
  5. package/dist/{Chat-BOX-03ho.d.ts → Chat-my5aJLz5.d.ts} +3 -3
  6. package/dist/{DepthViews-DSFyaSeL.d.cts → DepthViews-BBTv9VAs.d.cts} +1 -1
  7. package/dist/{DepthViews-Cviea-ri.d.cts → DepthViews-BLK0kgza.d.cts} +1 -1
  8. package/dist/{DepthViews-CgjX2AJ5.d.ts → DepthViews-CNqAyjne.d.ts} +1 -1
  9. package/dist/{DepthViews-D2dRqWRq.d.ts → DepthViews-G-7YRtOX.d.ts} +1 -1
  10. package/dist/animations/index.cjs +1 -1
  11. package/dist/animations/index.d.cts +3 -3
  12. package/dist/animations/index.d.ts +3 -3
  13. package/dist/animations/index.js +1 -1
  14. package/dist/{attachments-DHvnITuE.d.cts → attachments-CEmT52IA.d.cts} +5 -5
  15. package/dist/{attachments-oD5U2fXI.d.ts → attachments-CMNWE6YT.d.ts} +2 -2
  16. package/dist/{attachments-C_vtFu2f.d.ts → attachments-DU1djTNd.d.ts} +5 -5
  17. package/dist/{attachments-pIdGOQfm.d.cts → attachments-ZyjWsZdI.d.cts} +2 -2
  18. package/dist/{bookingCopy-CJir9mjb.d.ts → bookingCopy-BVhEt1bj.d.ts} +38 -200
  19. package/dist/{bookingCopy-DfuA13dh.d.cts → bookingCopy-ChZR5mtd.d.cts} +38 -200
  20. package/dist/bookingTypes-DCs8mmBb.d.cts +188 -0
  21. package/dist/bookingTypes-DCs8mmBb.d.ts +188 -0
  22. package/dist/{callCopy-rDH9JTFo.d.cts → callCopy-CPe9mbwG.d.cts} +7 -2
  23. package/dist/{callCopy-rDH9JTFo.d.ts → callCopy-CPe9mbwG.d.ts} +7 -2
  24. package/dist/{casesRepository-DHgIYKN9.d.ts → casesRepository-C5zrCcaE.d.ts} +1 -1
  25. package/dist/{casesRepository-B-Eg8ZJ3.d.cts → casesRepository-q2ak9AW0.d.cts} +1 -1
  26. package/dist/chat/index.cjs +1 -1
  27. package/dist/chat/index.d.cts +14 -10
  28. package/dist/chat/index.d.ts +14 -10
  29. package/dist/chat/index.js +1 -1
  30. package/dist/client/index.cjs +5 -5
  31. package/dist/client/index.d.cts +6 -6
  32. package/dist/client/index.d.ts +6 -6
  33. package/dist/client/index.js +5 -5
  34. package/dist/{config-ClI7QxB0.d.ts → config-DBpKJJbg.d.ts} +1 -1
  35. package/dist/{config-DAFbf721.d.cts → config-jocy3q66.d.cts} +1 -1
  36. package/dist/consent/index.cjs +1 -1
  37. package/dist/consent/index.d.cts +5 -5
  38. package/dist/consent/index.d.ts +5 -5
  39. package/dist/consent/index.js +1 -1
  40. package/dist/{context-B-dUM1Qf.d.cts → context-BfVBRgro.d.cts} +5 -5
  41. package/dist/{context-CZbgF_OH.d.ts → context-C3n9s4aG.d.ts} +5 -5
  42. package/dist/{copy-CneuYdrO.d.cts → copy-IysFx8yL.d.cts} +2 -2
  43. package/dist/{copy-CeRH3NXy.d.ts → copy-KP91zgtW.d.ts} +2 -2
  44. package/dist/core/index.cjs +7 -7
  45. package/dist/core/index.d.cts +12 -12
  46. package/dist/core/index.d.ts +12 -12
  47. package/dist/core/index.js +7 -7
  48. package/dist/{depth-DO3Ko_qs.d.ts → depth-BsMLLrQo.d.ts} +2 -2
  49. package/dist/{depth-B3NFKlF9.d.cts → depth-O9kiVUjs.d.cts} +2 -2
  50. package/dist/{entitlementRepository-74kW5mK0.d.ts → entitlementRepository-CHsY2kF3.d.ts} +2 -2
  51. package/dist/{entitlementRepository-m6zLUmiB.d.cts → entitlementRepository-DIG16pso.d.cts} +2 -2
  52. package/dist/{identity-C7uxGwfT.d.cts → identity-D7GKfOE7.d.cts} +1 -1
  53. package/dist/{identity-CLAUq-K4.d.ts → identity-k8nCQ_qM.d.ts} +1 -1
  54. package/dist/index.cjs +6 -6
  55. package/dist/index.d.cts +13 -13
  56. package/dist/index.d.ts +13 -13
  57. package/dist/index.js +6 -6
  58. package/dist/notifications/index.cjs +2 -2
  59. package/dist/notifications/index.d.cts +2 -2
  60. package/dist/notifications/index.d.ts +2 -2
  61. package/dist/notifications/index.js +2 -2
  62. package/dist/{pets-BoB5B7kp.d.cts → pets-Bod_DDiW.d.cts} +1 -1
  63. package/dist/{pets-w4M9d-Dn.d.ts → pets-C_QX8s5f.d.ts} +1 -1
  64. package/dist/{petsRepository-DZZoEG3r.d.cts → petsRepository-B3vr628I.d.cts} +1 -1
  65. package/dist/{petsRepository-BjKJ8UsN.d.ts → petsRepository-DdzgINfF.d.ts} +1 -1
  66. package/dist/photo/index.cjs +1 -1
  67. package/dist/photo/index.js +1 -1
  68. package/dist/{ports-C2EVK416.d.cts → ports-BSm48zIC.d.cts} +16 -5
  69. package/dist/{ports-BO3bzTJg.d.ts → ports-DHhxhn95.d.ts} +16 -5
  70. package/dist/records/depth/index.cjs +2 -2
  71. package/dist/records/depth/index.d.cts +7 -7
  72. package/dist/records/depth/index.d.ts +7 -7
  73. package/dist/records/depth/index.js +2 -2
  74. package/dist/records/index.cjs +3 -3
  75. package/dist/records/index.d.cts +10 -10
  76. package/dist/records/index.d.ts +10 -10
  77. package/dist/records/index.js +3 -3
  78. package/dist/{requestFunnel-D7ipek3e.d.ts → requestFunnel-CTG-wNc3.d.ts} +2 -2
  79. package/dist/{requestFunnel-Djjwgoe9.d.cts → requestFunnel-Dcjyh0j2.d.cts} +2 -2
  80. package/dist/{runtimeTypes-D2C9uD85.d.cts → runtimeTypes-4qptUulD.d.cts} +4 -4
  81. package/dist/{runtimeTypes-CTL3yToK.d.ts → runtimeTypes-CmpdQaE2.d.ts} +4 -4
  82. package/dist/server/events/index.cjs +1 -1
  83. package/dist/server/events/index.d.cts +2 -2
  84. package/dist/server/events/index.d.ts +2 -2
  85. package/dist/server/events/index.js +1 -1
  86. package/dist/server/index.cjs +2 -2
  87. package/dist/server/index.d.cts +3 -3
  88. package/dist/server/index.d.ts +3 -3
  89. package/dist/server/index.js +2 -2
  90. package/dist/televet/booking/index.cjs +1 -1
  91. package/dist/televet/booking/index.d.cts +31 -17
  92. package/dist/televet/booking/index.d.ts +31 -17
  93. package/dist/televet/booking/index.js +1 -1
  94. package/dist/televet/call/index.cjs +1 -1
  95. package/dist/televet/call/index.d.cts +13 -1
  96. package/dist/televet/call/index.d.ts +13 -1
  97. package/dist/televet/call/index.js +1 -1
  98. package/dist/televet/index.cjs +1 -1
  99. package/dist/televet/index.js +1 -1
  100. package/dist/testing/index.cjs +3 -3
  101. package/dist/testing/index.d.cts +1 -1
  102. package/dist/testing/index.d.ts +1 -1
  103. package/dist/testing/index.js +3 -3
  104. package/dist/testing/rn/index.cjs +1 -1
  105. package/dist/testing/rn/index.d.cts +7 -7
  106. package/dist/testing/rn/index.d.ts +7 -7
  107. package/dist/testing/rn/index.js +1 -1
  108. package/dist/testing/web/index.cjs +1 -1
  109. package/dist/testing/web/index.d.cts +7 -7
  110. package/dist/testing/web/index.d.ts +7 -7
  111. package/dist/testing/web/index.js +1 -1
  112. package/dist/{timelineRows-CzR1T2DR.d.cts → timelineRows-BkLhRUDt.d.cts} +1 -1
  113. package/dist/{timelineRows-7yFNK_rU.d.ts → timelineRows-Dp4ZSnAo.d.ts} +1 -1
  114. package/dist/{uploadTransport-DKJHs3Yj.d.cts → uploadTransport-C1p8FuG8.d.cts} +5 -1
  115. package/dist/{uploadTransport-D0M0T4hN.d.ts → uploadTransport-bZIKBTyK.d.ts} +5 -1
  116. package/dist/{useRecordsDepth-Bnw36jPr.d.cts → useRecordsDepth-DRNry1r9.d.cts} +1 -1
  117. package/dist/{useRecordsDepth-DFF7Faxx.d.ts → useRecordsDepth-DnDhTaUL.d.ts} +1 -1
  118. package/dist/video/index.cjs +1 -1
  119. package/dist/video/index.js +1 -1
  120. package/dist/{view-BqcOYFTr.d.ts → view-CwuOPIJc.d.ts} +7 -4
  121. package/dist/{view-CaJs8WFH.d.cts → view-D4VdPKCa.d.cts} +7 -4
  122. package/dist/web/consent/index.cjs +1 -1
  123. package/dist/web/consent/index.d.cts +5 -5
  124. package/dist/web/consent/index.d.ts +5 -5
  125. package/dist/web/consent/index.js +1 -1
  126. package/dist/web/index.cjs +10 -10
  127. package/dist/web/index.d.cts +14 -14
  128. package/dist/web/index.d.ts +14 -14
  129. package/dist/web/index.js +10 -10
  130. package/dist/web/notifications/index.cjs +2 -2
  131. package/dist/web/notifications/index.d.cts +3 -3
  132. package/dist/web/notifications/index.d.ts +3 -3
  133. package/dist/web/notifications/index.js +2 -2
  134. package/dist/web/records/depth/index.cjs +2 -2
  135. package/dist/web/records/depth/index.d.cts +7 -7
  136. package/dist/web/records/depth/index.d.ts +7 -7
  137. package/dist/web/records/depth/index.js +2 -2
  138. package/dist/web/records/index.cjs +3 -3
  139. package/dist/web/records/index.d.cts +9 -9
  140. package/dist/web/records/index.d.ts +9 -9
  141. package/dist/web/records/index.js +3 -3
  142. package/dist/web/televet/booking/index.cjs +2 -2
  143. package/dist/web/televet/booking/index.d.cts +41 -52
  144. package/dist/web/televet/booking/index.d.ts +41 -52
  145. package/dist/web/televet/booking/index.js +2 -2
  146. package/dist/web/televet/call/index.cjs +1 -1
  147. package/dist/web/televet/call/index.d.cts +12 -5
  148. package/dist/web/televet/call/index.d.ts +12 -5
  149. package/dist/web/televet/call/index.js +1 -1
  150. package/dist/web/televet/index.cjs +4 -1
  151. package/dist/web/televet/index.d.cts +31 -2
  152. package/dist/web/televet/index.d.ts +31 -2
  153. package/dist/web/televet/index.js +4 -1
  154. package/package.json +4 -3
package/CHANGELOG.md CHANGED
@@ -10,6 +10,381 @@ changes that has not been earned by any production integration yet.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.5.2] - 2026-09-25
14
+
15
+ Fixes for the televet booking, web vet call and web dialog defects found on staging on 2026-09-23, for the
16
+ React Native vet call, and for the records upload and chat defects the partner end-to-end runs found on
17
+ 2026-09-24, with the missing React Native pull to refresh on a records request. Public signature changes are
18
+ additive only, each an optional prop, a new method, a new export or a promise from a method that returned
19
+ nothing: `selectedSlot` on `TelevetSlotPicker` (both platforms); `consultMinutes` on the React Native
20
+ `EverfurVetCall` and `EverfurVetCallLobby` (`@everfur/sdk/televet/call`, see the call lobby entry under Fixed);
21
+ `ChatController.newConversation()` (see Added); a promise from `refresh()` on `useEverfurRecords` (both
22
+ platforms, see Changed); and on web, `onAddToCalendar` on `EverfurTelevetHost` and `EverfurTelevetDialog`,
23
+ `returnedTo` on `TelevetVisitsScreen`, `lengthMinutes` on `TelevetBookedScreen`, a ready `.ics` calendar writer
24
+ exported from `@everfur/sdk/web/televet`, and a third result a web `TelevetCalendarWriter` may resolve,
25
+ `'handed-off'` (a writer that resolves a boolean is unchanged).
26
+
27
+ ### Added
28
+
29
+ - **`EverfurTelevetHost` takes `onAddToCalendar` (web).** The booked confirmation has had a calendar seam since
30
+ the web booking shipped, but the default in-page visit could not reach it: the host took no props and its
31
+ dialog rendered the flow without a writer, so a partner on the default path could not offer Add to Calendar
32
+ even with a calendar of its own. Pass a `TelevetCalendarWriter` to `EverfurTelevetHost` and the confirmation
33
+ offers the control and calls the writer with the visit's entry; without one it has no calendar control, as
34
+ before. The hosts `VetVisitButton` and `EverfurChat` mount take no props, and the first host mounted renders,
35
+ so mount yours above them. `EverfurTelevetHostProps`, `TelevetCalendarEvent` and `TelevetCalendarWriter` are
36
+ exported from `@everfur/sdk/web/televet` (types only; the entry's code does not grow by the booking screens).
37
+ `EverfurTelevetDialog` takes the same prop.
38
+ - **`downloadTelevetCalendarFile`, a ready calendar writer (web).** The consumer web app's Add to Calendar hands
39
+ the member a one-shot `.ics` file, because a browser has no calendar a page may write to. The SDK now ships
40
+ that file's builder, ported from the consumer's: `<EverfurTelevetHost onAddToCalendar={downloadTelevetCalendarFile} />`
41
+ offers Add to Calendar with no calendar code of your own. The file is RFC 5545 in UTC with CRLF line ends and
42
+ 75-octet folding, its `UID` is the entry's `key`, and it carries no alarm (a file cannot be withdrawn when a
43
+ visit moves). An entry that would be wrong (a blank title, an unreadable time, an end not after the start) is
44
+ not built, and the confirmation shows its existing did-not-save line. A started download is not an entry in a
45
+ calendar (the member still imports the file, and may cancel the save or have it blocked), so the writer resolves
46
+ `'handed-off'`, a result the web `TelevetCalendarWriter` now allows beside `true` and `false`, and on it the
47
+ confirmation keeps Add to Calendar enabled to press again, as the consumer web app's link does, rather than
48
+ saying the visit was added. Only a writer that resolves `true` gets "Added to your calendar".
49
+ `televetCalendarFile(event)` returns the same file as `{ href, filename }` for a page that renders its own link.
50
+ Both are exported from `@everfur/sdk/web/televet` with `TelevetCalendarFile`; no new copy.
51
+ - **`TelevetSlotPicker` takes an optional `selectedSlot`.** The strip opens on that slot's day instead of
52
+ the first offered day when the day is offered. Omitted, the picker behaves as before. Both platforms.
53
+ - **`ChatController.newConversation()`.** Leave the active thread for a fresh, empty one. It is what
54
+ `newConversation()` on `useEverfurChat` now calls. `adoptConversation(null)` is unchanged and still clears the
55
+ id alone. Both platforms.
56
+
57
+ ### Changed
58
+
59
+ - **`refresh()` on `useEverfurRecords` returns a promise.** It settles once the reads it started have landed,
60
+ which is what the React Native request screen's pull-to-refresh spinner waits for. It was typed `void`; a
61
+ caller that ignores the result is unaffected. Both platforms.
62
+
63
+ ### Fixed
64
+
65
+ - **The chat follows the member to the answer.** On the web, after a send the new question and its reply
66
+ rendered below the visible transcript. The transcript followed new content only while it believed the reader
67
+ was at the end, and three things broke that: it read its own scroll, a frame later, as the reader's, and on a
68
+ resumed thread the follow-up list had grown under the reply by then, so the reader looked 709 px short of the
69
+ end; nothing set it back on a send; and when it did follow, it went to the end of the follow-up list, which put
70
+ the question and its answer above the view. The transcript now settles on the latest turn, the top of the
71
+ newest reply with the question above it, as Everfur's own apps do (`resolveTurnScrollTarget`, ported). Every
72
+ turn the member starts (a typed send, a suggested prompt, Retry) takes them there even from further up, and the
73
+ surface no longer mistakes its own scroll for the reader's. A reader who scrolls up to re-read still keeps their
74
+ place when a message they did not ask for arrives. The React Native thread never scrolled itself at all, so a
75
+ resumed thread opened on its first message and every send landed below the fold; it now follows the same way,
76
+ measured with `onLayout` as the Everfur app measures its own thread. Both platforms.
77
+ - **New chat clears the thread.** `newConversation()` on `useEverfurChat` (behind the new-chat controls in the
78
+ header and the history panel, and `Start a new chat` under a turn that cannot be retried) only cleared the
79
+ conversation id. The old thread stayed on screen, and the next message opened a new conversation on the wire
80
+ but rendered under the old one. It now leaves the thread: the transcript clears with its history cursor, a
81
+ failed or stopped turn and the reply's own follow-ups; a reply still streaming into it is dropped; and the next
82
+ message is the first turn of the new conversation. The suggested prompts the chat opened with stay, as does the
83
+ unread dot for the member's other threads. Both platforms.
84
+ - **A chat photo uploads on an Expo app (React Native).** On a partner's Expo SDK 57 app every picked photo
85
+ failed: the initiate call answered 200 three times and no request reached S3, then the composer held "Some
86
+ attachments failed to upload. Remove or retry them before sending." Expo replaces the global `fetch` with
87
+ `expo/fetch`, whose multipart encoder throws on React Native's `{ uri, name, type }` file part before any
88
+ request exists, and the transport posted through the global `fetch`. `createRnChatAttachmentTransport` now
89
+ posts through React Native's own `XMLHttpRequest` (Expo leaves it in place), which streams the file from disk
90
+ as before: the policy fields in the server's order, the file last, no cookie, no header of its own, and the
91
+ same two bounded rejections. The `fetch` option is still honoured when given.
92
+ - **A failed attachment draws its retry control (React Native).** The alert said "Remove or retry", but the red
93
+ overlay that retries (`Retry uploading {filename}`) was never drawn, and neither were the uploading wash or the
94
+ upload flash. Their box came from `StyleSheet.absoluteFillObject`, which React Native 0.86 no longer has; a
95
+ spread of it is `{}`, so each overlay fell into the flow at zero size. The same spread placed the chat header's
96
+ title layer and the feedback sheet's scrim. They now use a literal box that holds on every React Native version.
97
+ - **The React Native vet call keeps its video and camera-off panels full-size.** `EverfurVetCall` took the box
98
+ for the clinician's video and its frame, the self-view video, the self-view's camera-off overlay and the
99
+ clinician's camera-off panel from the same `StyleSheet.absoluteFillObject` spread, so on React Native 0.86 each
100
+ fell into the flow at zero size during a call. They now use the same literal box. The scrims behind the chat
101
+ history drawer, the pet switcher and the records edit and share sheets used `StyleSheet.absoluteFill`, which
102
+ still works, and now use that box too, so no React Native surface takes an overlay box from `StyleSheet`.
103
+ - **Photo attach works under React StrictMode (web, and any React Native host that uses it).** In development
104
+ under `<StrictMode>` (on by default in Vite and Next), every valid photo was refused with "You can attach up to
105
+ 5 files per message." at 0 of 5, and nothing uploaded. StrictMode's remount ran the hook's cleanup, which
106
+ disposed the attachment queue the hook kept handing out. The remount now builds the scope a fresh queue.
107
+ Production builds were not affected.
108
+ - **Booking a slot again after cancelling it books a new visit.** A member booked a slot, cancelled the visit
109
+ in-app, then booked the same slot again. The hold and the booking both succeeded, but the booking answer was
110
+ the first visit, now cancelled, and the flow confirmed it ("You're booked with {vet}" on the web, "You are
111
+ booked." on React Native). No new visit existed. The booking's `Idempotency-Key` was derived from the slot
112
+ alone (`televet.book.<slotId>`), so the platform read the second booking as a retry of the first and
113
+ answered with the first consult as it now stood.
114
+ `televetBookingKey` now mints a new key on every call, and both platforms call it once per awarded hold, so a
115
+ new hold is a new booking even on the same slot. Pressing Confirm again after a failure still reuses that
116
+ hold's key, so a lost answer cannot become a second visit. If you drive the booking reducer yourself, call
117
+ `televetBookingKey` once when a hold is awarded and keep the result in the draft; calling it at submit time
118
+ would give each retry its own key. Both platforms.
119
+ - **A booking answered with a visit that is not live is never confirmed.** `TelevetBookingController.book`
120
+ used to settle any readable 2xx as a booking. It now settles a consult outside the platform's live statuses
121
+ (`scheduled`, `in_progress`, `awaiting_summary`) as a refusal: a cancelled, no-show, completed or abandoned
122
+ visit, or a status this build does not know. The booking flow shows it as the existing slot-taken refusal
123
+ ("That time was just taken. Pick another.") and returns to the time grid, where picking a time takes a new
124
+ hold, and so a new key. `onBooked` is not called. The flow checks the same thing before it draws the
125
+ confirmation, so a controller you inject through the `controller` prop is held to it too. Both platforms.
126
+ - **A booking whose key the platform refuses goes back to the time grid, not round Confirm.** From EFBackend
127
+ #1480 the platform answers a booking whose `Idempotency-Key` names a visit that has ended, or a live visit on
128
+ another slot or for another pet, with 409 `IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST`, and records nothing.
129
+ The booking flow read that as a retryable failure and left Confirm live on the review step, and Confirm
130
+ resent the same key, so every press got the same 409. `TelevetBookingController.book` now gives that refusal
131
+ the reason code `televet_booking_key_reused`, and the flow shows it as the existing slot-taken refusal ("That
132
+ time was just taken. Pick another.") on the time grid, where picking a time takes a new hold, and so a new
133
+ key. `onBooked` is not called. The error code stays `idempotencyConflict` and is still not retryable, and no
134
+ other route's `idempotencyConflict` changes meaning. A controller you inject through the `controller` prop
135
+ gets the same remedy. Both platforms.
136
+ - **Going Back from review and picking the same time no longer sends a second hold.** The time step took a
137
+ new hold on every pick, including the time the member already held. The platform claims a hold only on an
138
+ open slot, so it answered 409: the member read "That time was just taken. Pick another." about their own
139
+ hold, the chip stayed listed, and every press spent one of the member's hourly holds. A pick of the slot the
140
+ draft already holds, unexpired, now goes straight back to review with the same hold and booking key, as
141
+ consumer web does. After a real 409 on a hold the flow re-reads the times, so the one that went leaves the
142
+ grid. A booking refused as slot-taken (a taken slot, a lapsed hold, a visit that is not live, a reused key)
143
+ now also drops the draft's hold and key and re-reads the times, so the next pick, even of the same time, is
144
+ a fresh hold with a fresh key. Both platforms.
145
+ - **A hold refused for any reason but a conflict no longer says the time was taken.** A rate limit (429), a
146
+ server fault or a dropped connection on the hold showed "That time was just taken. Pick another." The time
147
+ step now shows the existing line "That did not book. Try again." for all three. A 402 on a hold now opens
148
+ the paused state, as a 402 at Confirm does. The time stays on the grid. No new strings. Both platforms.
149
+ - **A rate limit never shows the server's sentence: the booking steps and the visits list show their existing
150
+ lines.** A 429 at Confirm printed the platform's own sentence under Confirm, and since 0.5.1 a rate-limited
151
+ visits list printed it in its error card. That sentence carries an em-dash. By the owner's decision of
152
+ 2026-09-24, one rule everywhere, as consumer web does: the time step and the review step show the existing
153
+ line "That did not book. Try again." for a 429, and the visits list shows the card it shows for any other
154
+ failed read, "Could not load your visits" with "Check your connection and try again." (web), or the title
155
+ alone (React Native), each with its Try again button. The visits list still settles after one request, as
156
+ 0.5.1 made it. The review step's line keeps the test id `televet-review-rate-limited`.
157
+ `TelevetBookingFailure` still names the case `rate_limited`, and `televetBookingRefusal` still carries the
158
+ server's `message` for a host that reads it. No new strings. Both platforms.
159
+ - **Back from review keeps the day of the held time.** The day strip reopened on the first offered day, with
160
+ the member's own time out of sight. It now opens on the held slot's day and scrolls that day into the middle
161
+ of the strip, once, when the strip first draws it. Only the strip scrolls, never the page around it. Both
162
+ platforms.
163
+ - **A booking refused at Confirm keeps the member's day (web).** After a slot-taken refusal at Confirm (a taken
164
+ slot, a reused key, a visit that is not live) the time step reopened on the first offered day, because the
165
+ refusal clears the held slot the strip took its day from. It now reopens on the refused time's day while that
166
+ day still has times, and on the first offered day only when it has none, as consumer web does. The hold and
167
+ its booking key are still dropped, so the next pick is a fresh hold under a new key. No new strings.
168
+ - **The review states the visit length the platform serves.** The review's length row printed the SDK's
169
+ `CONSULT_MINUTES` (10) whatever the held slot said. It now reads the held slot's own interval
170
+ (`ends_at - starts_at`), which is the length the platform runs the visit for (15 minutes on the platform's
171
+ default grid since 2026-09-18, and 20, 30 or 60 for a clinician on longer slots) and the one consumer web's
172
+ review shows. It is capped at 60 minutes, as the platform caps it, and falls back to `CONSULT_MINUTES` only
173
+ for a slot that does not say. It is the only length served before a booking exists: `call_limits` arrives
174
+ with the join. Both platforms.
175
+ - **The calendar entry ends when the booked visit does (web).** The booked confirmation's calendar entry ended
176
+ `CONSULT_MINUTES` (10) after the start whatever the booked slot said, so on the platform's 15-minute default
177
+ grid every visit was written 5 minutes short, into the `.ics` file's `DTEND` and into the `endDate` a partner's
178
+ own `TelevetCalendarWriter` receives. It now ends after the booked slot's served length, the same one the
179
+ review states; consumer web ends its file at the booked slot's served end. `TelevetBookedScreen` takes the
180
+ length as an optional `lengthMinutes`, which the prebuilt flow passes; a screen mounted without it keeps
181
+ `CONSULT_MINUTES`. React Native's confirmation still uses the constant.
182
+ - **Leaving the in-page vet call returns to the visits instead of a blank dialog (web).** In 0.5.1, pressing the
183
+ lobby's own Leave left the visit dialog open, modal and blank, with no close control, and Escape did nothing;
184
+ the partner page behind it stayed blocked. `EverfurVetCall` removed Daily's Prebuilt iframe from the page
185
+ before awaiting `leave()`, and Daily's `leave()` only settles when that iframe answers, so it never settled,
186
+ `destroy()` never ran and `onLeave` never fired. Every Leave the SDK draws now keeps the frame in the page until
187
+ Daily has let go, waits at most 5 seconds for it, reports the departure once however often it is pressed, and
188
+ releases the Daily instance, so the member can join again without a reload. That includes a Leave that hit the
189
+ 5 second bound and a call the host unmounted mid-leave, which used to leave the instance registered (daily-js
190
+ then refused the next frame on the page), and a host that unmounts the call mid-leave is no longer called back
191
+ afterwards. After Daily's own Leave the instance is released at once as well, not only when the host unmounts
192
+ the call.
193
+ - **The in-page join asks the recording notice BEFORE it calls `POST /consults/{id}/join` (web).** That request
194
+ mints the token and records the member's arrival for the clinician, and the join screen sent it on mount, before
195
+ the notice, so a member who pressed "Not now" had already been logged as arriving. The order is now the
196
+ consumer web app's: the notice first, the request only from "Continue to the call" (the notice stays up,
197
+ controls disabled, while it travels), and "Not now" sends nothing. A page without the Daily module registered
198
+ says the visit cannot be joined before any notice or request. The acceptance covers one consult and one visit
199
+ into the room: a join screen handed another consult, or reached again after leaving, asks again before it
200
+ sends anything. The call surface does not ask a second time behind the join screen; that is an internal
201
+ receipt, not a prop, and `EverfurVetCall` still has no prop that skips the notice.
202
+ - **The web call is Daily's frame, with no SDK lobby (web).** `EverfurVetCall` no longer stacks the lobby panel
203
+ (and its second Leave and a hardcoded "10 MINUTE VISIT") under the frame, matching the consumer web app's owner
204
+ room: in the room, Daily's own tray carries Leave. Daily's rename control is off, as it is there. The frame's
205
+ height is capped at `calc(100dvh - 8rem)`, as the consumer web app caps its owner room, so in a wide container
206
+ Daily's tray (and its Leave) stays above the fold; before, a 16:9 frame at full width could be as tall as the
207
+ viewport. `clinicianName` and `petName` are still accepted and are not shown on the web.
208
+ `EverfurVetCallLobby` is still exported for a host that draws its own. The failure card and its Leave are
209
+ unchanged.
210
+ - **A way out before the room (web).** While the call was joining (Daily's frame loading, Daily's own pre-join
211
+ screen, which has a Join and no Leave, or a frame the page never let load) the member's only control was Join:
212
+ the dialog has no close control and Escape was swallowed. `EverfurVetCall` now draws its Leave under the frame
213
+ until Daily reports the member in the room, and removes it there.
214
+ - **Daily's pre-join fits the frame on a phone (web).** Daily's pre-join screen lays itself out by the frame's
215
+ shape, and the frame was 16:9 at every width, so at phone width Daily drew its side-by-side layout into 390 or
216
+ 358 px: Join and the heading ran about 34 px past the right edge and the camera and microphone toggles went off
217
+ the left edge (iPhone 14 emulation, Chromium and WebKit). On a phone in landscape the height cap cut the toggle
218
+ row by 43 px. Until the member is in the room, `EverfurVetCall` now sizes the frame by its own width: narrower
219
+ than 480 px it is portrait (3:4) and at least 500 px tall, so Daily stacks its pre-join; from 480 px it stays
220
+ 16:9 under the cap, with a floor of 45% of its width (at least 270 px) that only binds where the cap would cut
221
+ the toggle row, and the page scrolls there. In the room the frame is the 16:9 box under the cap, as before.
222
+ `scripts/measure-call-prejoin.mjs` measures it against Daily's real Prebuilt.
223
+ - **Escape on the in-page join screen is Leave everywhere except in the room (web).** It was swallowed for as
224
+ long as the call module was loaded: at Daily's pre-join screen, on the failure card and after the member had
225
+ left. It is now swallowed only between Daily's `joined-meeting` and the departure, so it never hangs up a call,
226
+ and while the join request travels, as the consumer web app holds its decline then.
227
+ - **Coming back to the visits puts focus on them (web).** Closing a booking started from the visits, declining
228
+ the recording notice, leaving the call or moving a visit popped the screen that held focus, and nothing on the
229
+ visits took it, so focus fell to `<body>` with the dialog still open: a screen reader said nothing. The
230
+ visits' first heading (Upcoming visits, else Past visits) now takes focus once the list has loaded, as the
231
+ booking steps' headings do, or the error notice when the list fails to load. It is `tabIndex={-1}`, so never a
232
+ tab stop. The flow's first screen still takes no focus from your page, and focus the member moved while the
233
+ list loaded stays where it is. `EverfurTelevet` tells the screen through its new `returnedTo` prop; a host
234
+ that mounts `TelevetVisitsScreen` in its own router can pass it the same way.
235
+ - **The recording notice takes focus when it appears (web).** Pressing Join on a visit replaced the visits with
236
+ the recording notice, and pressing Continue replaced the notice with the busy one the join request waits
237
+ behind. Each time the pressed control went with its screen and focus fell to `<body>` with the dialog still
238
+ open (the partner web call run of 2026-09-24, SDK main 62998c4). The notice's `alertdialog` now takes that
239
+ focus when it mounts, so it is announced by its title and Tab reaches Continue next, and the busy notice takes
240
+ it again while the request travels, when both its buttons are disabled. It is `tabIndex={-1}`, so never a tab
241
+ stop, and it takes focus only from `<body>`, never from a control the member is on. The same run found focus on
242
+ `<body>` after Not now, the pre-join Leave and Daily's own Leave; those returns land on the visits heading
243
+ (the entry above), now pinned through the whole call path.
244
+ - **Closing a dialog under React StrictMode returns focus to the control that opened it (web).** In development
245
+ StrictMode runs a mount effect twice with a simulated unmount between. The shared dialog hook restored focus
246
+ in that simulated unmount, where the page behind the still-modal dialog refused it, forgot the opener, and on
247
+ the re-run recorded the dialog itself as the opener; when the dialog really closed, focus fell to `<body>`.
248
+ The opener is now the control captured before `showModal()`, kept until focus actually lands on it. Every web
249
+ dialog shares the hook: the in-page vet visit, the chat sheets and the records sheets. That includes the visit
250
+ opened from inside `EverfurChat` (the urgency banner's Find a vet and the header's Talk to a vet), where the
251
+ partner web chat run of 2026-09-24 saw focus land on the page after the close. Production builds were not
252
+ affected.
253
+ - **`EverfurVetCallLobby` states the served visit length or none (web).** It defaulted to a hardcoded 10 minutes
254
+ (`VET_CALL_CONSULT_MINUTES`), which is wrong for any slot that is not 10 minutes long. Pass the join response's
255
+ `callLimits.consultMinutes`; without `consultMinutes` the duration line is not drawn.
256
+ - **React Native: `onLeave` fires once when the member leaves the vet call.** daily-js emits `left-meeting`
257
+ before `leave()` settles, so `EverfurVetCall` reported the departure from that event and again from the Leave
258
+ control, and a host that navigates on `onLeave` navigated twice. Every departure path now reports through one
259
+ guard, the consumer app's fix of 2026-09-13. On the member's own Leave the report waits for `leave()` and
260
+ `destroy()` to settle, so the surface stays mounted while Daily releases the camera and microphone, and that
261
+ wait is bounded at 5 seconds, so a `leave()` that never settles can no longer strand the member. A second
262
+ press while leaving is the same departure, and the recording notice's Leave reports once too. Nothing is
263
+ reported once the host has taken the surface down, so a departure that settles late, or a room the host
264
+ replaced while it was still leaving, cannot send a stale `onLeave` after the member has moved on.
265
+ - **React Native: the call lobby states the visit length the server sold.** The duration line always read
266
+ `VET_CALL_CONSULT_MINUTES` (10), while the join response's `call_limits.consult_minutes` was parsed and then
267
+ dropped. `TelevetJoinScreen` now passes the served value to the call, the lobby quotes it through the existing
268
+ `VET_CALL_COPY.lobbyDuration`, and the in-call clock turns to the danger colour at that same length, as the
269
+ consumer app does. With no served value, or one that is not a whole number of at least 1, both are unchanged.
270
+ One optional prop is added for this, and no existing signature changes: `EverfurVetCall` and
271
+ `EverfurVetCallLobby` (`@everfur/sdk/televet/call`) take `consultMinutes`, the length the join response served
272
+ (`VetCallCredential.callLimits?.consultMinutes`). A host that mounts the call itself can pass it; absent, the
273
+ lobby says what it said before.
274
+ - **React Native: the recording notice comes before `POST /consults/{id}/join`, and Leave on it sends nothing.**
275
+ That POST mints the room token and records the member's arrival for the clinician, and `TelevetJoinScreen`
276
+ sent it on mount, before the call surface showed "This consultation is recorded.", so a member who pressed
277
+ Leave on the notice had already been logged as arriving (the web join had the same order, known issue k4).
278
+ The join screen now shows the notice first and sends the join only on "Continue to the call", once per press.
279
+ While the request travels the notice stays up with Continue busy, and Leave still leaves, because iOS has no
280
+ back button; an answer that arrives after Leave opens nothing. The call surface is told the member already
281
+ accepted through an internal receipt for the room just minted, so it does not ask twice. There is no prop for
282
+ this: `EverfurVetCall` mounted by a host still asks on every mount, whatever props it is given. An app without
283
+ the Daily modules registered is told it cannot join before any notice or request, and the setup instruction
284
+ still reaches the console. No public signature changes.
285
+ - **React Native: an acceptance of the recording notice covers one consult and one visit into its room.**
286
+ `TelevetJoinScreen` handed another `consultId` while mounted now asks the notice again before that consult's
287
+ join is sent, and so does a consult it is handed back: an earlier Continue for a consult is spent once the
288
+ screen shows another, so returning to it never sends its join without a new press. When the call reports that the member left or the vet ended it, the join screen lets the room
289
+ go, so a host that keeps the screen mounted after `onLeave` shows the notice again, not a finished call that
290
+ still swallows the back press. `EverfurVetCall` handed another `roomUrl` while mounted is a fresh surface: it
291
+ asks the notice again before joining the new room, leaves and destroys the first, and carries no participant,
292
+ clock or reported departure across. No public signature changes.
293
+ - **React Native: the vet call is destroyed even when `leave()` never settles.** The teardown awaited `leave()`
294
+ before calling `destroy()`, so a `leave()` that hung meant `destroy()` was never called: the 5 second bound
295
+ reported the departure while the Daily call object stayed alive, possibly with the microphone and camera, and
296
+ a rejoin in the same session could throw "Duplicate DailyIframe instances are not allowed". `EverfurVetCall`
297
+ now waits for `leave()` for at most those 5 seconds, then, whether it settled, failed or timed out, turns the
298
+ microphone and camera off (`setLocalAudio(false)`, `setLocalVideo(false)`) and calls `destroy()`. `onLeave`
299
+ still fires once, after `destroy()` has been called. One limit remains in daily-js itself: its `destroy()`
300
+ begins with a `leave()` of its own, so a call object that never answers at all can still hold `destroy()`
301
+ open and ignore the device calls. No public signature changes.
302
+ - **React Native: the in-app vet call opens with the real `@daily-co/react-native-daily-js`.** In 0.5.0 and
303
+ 0.5.1 it never opened: every install registered as the docs say failed the SDK's shape check ("optional peer
304
+ '@daily-co/react-native-daily-js' is present but failed its shape check"). On the real package `default` is
305
+ the Daily class and `createCallObject` a static of it, and the check read `default` only when it was a plain
306
+ object, which is what its tests had stood in for it. It now reads a class default as well, calls
307
+ `createCallObject` on the class, and a contract suite holds it to the real package's own dist entry. No
308
+ public signature changes.
309
+ - **React Native: an app whose Daily module does not load is told so before the recording notice.** The join
310
+ screen asked only whether a module was registered, so a registered module that then failed to load showed the
311
+ notice, sent `POST /consults/{id}/join` (which stamps the member's arrival for the clinician) and only then
312
+ refused. It now asks the call's own guarded load first, and refuses with Leave before any notice or request.
313
+ - **React Native: the join screen's refusals stay inside the safe area and the screen gutter.** The SDK's modal
314
+ dropped its safe-area padding as soon as the call module loaded, so a call that then could not open drew its
315
+ refusal under the status bar. The padding now goes only when the room itself renders. The recording notice,
316
+ the refusals on the join screen and the reschedule screen now sit in the same side gutter as the visits and
317
+ the visit screens.
318
+ - **A records upload works on an Expo app (React Native).** On a partner's Expo SDK 57 app every records upload
319
+ failed and the member saw "Retry 1 that didn’t upload". It is the chat photo defect above:
320
+ `createRnRecordsUploadTransport` posted React Native's `{ uri, name, type }` FormData part through the global
321
+ `fetch`, which Expo replaces with `expo/fetch`, and its multipart encoder throws "Unsupported FormDataPart
322
+ implementation" on that part before any request exists. The records transport now posts through React Native's
323
+ own `XMLHttpRequest` too, and both transports share one request module, so records and chat make the same
324
+ request. The file still streams from disk, the policy fields still go first and the file last, no
325
+ cookie rides the request, and every failure still rejects with the same bounded message. `onProgress` still
326
+ reports completion only. The `fetch` option is unchanged and still wins when given.
327
+ - **A failed records upload row shows the file, "Failed" and why (React Native).** The row drew blank, with only
328
+ "Retry 1 that didn’t upload" under it. The file name, the status label and the reason shared one flex row, and
329
+ the reason, which could not shrink, kept the whole line and left the name and "Failed" 0 pt wide. The reason
330
+ now sits under the name and the label. The uploading row's progress bar, which sat in the same row the same
331
+ way, is under them too.
332
+ - **A refused records file is named with why, and never opens an empty request.** On the web upload card,
333
+ picking ONE file that is not a PDF, or ONE PDF over 25 MB, showed the connection line ("We couldn’t upload
334
+ that record. Check your connection and try again.") although nothing was sent, while the same files picked
335
+ together showed the skipped-files message. The card now shows the skipped-files message for whatever it
336
+ refused, however many files were picked. On both platforms the card created the owner request before it
337
+ looked at the pick, so a pick where every file was refused left an empty request on the member's page. The
338
+ card now classifies the pick first, by the same PDF and 25 MB rule the upload applies, creates the request
339
+ only when at least one file will upload, and hands only those files to `uploadDocuments`. A React Native
340
+ picker handle without `sizeBytes` is refused before any request exists, as the `RecordsDocumentPicker`
341
+ contract already said. Both platforms.
342
+ - **React Native: pull to refresh on the records request screen.** The consumer app re-reads a request when the
343
+ member pulls its request screen down; the SDK request screen had no way to re-read a status such as "Checked
344
+ by our team" short of leaving and coming back. `EverfurRecordsRequest` now carries a `RefreshControl` that
345
+ re-reads what the screen loaded. As in the app, the spinner shows for the member's pull only, until the re-read
346
+ settles, never for the status poll, and the screen keeps the request on screen while it re-reads instead of
347
+ going back to its skeleton. The request list (`EverfurRecordsRequests`) has no pull to refresh, as the
348
+ consumer app's request list has none. No new copy.
349
+
350
+ ## [0.5.1] - 2026-09-23
351
+
352
+ Fixes from the partner end-to-end run of 2026-09-23 (a Vite React web app and an Expo app, built through the
353
+ MCP planner, against staging). No public signature changes.
354
+
355
+ ### Fixed
356
+
357
+ - **A partner member is told booking is paused, never that they need Pro.** On the partner plane the televet
358
+ entitlement read answers whether YOUR allowance is on, not whether the member has a plan. When it answered
359
+ `active: false` (allowance not set yet, suspended, set to 0 or used up) the booking flow opened, on both
360
+ platforms and from every vet entry, on "A Pro membership is needed to book a video visit.", with Close as the
361
+ only control. That state now renders what the unavailable branch renders: your `renderUnavailable` when you
362
+ pass one, else the paused card ("Booking is not available right now."). A 402 at Confirm joins the same state
363
+ instead of printing the membership sentence under Confirm. The test id `televet-booking-not-entitled` and the
364
+ optional "Check availability" escape are unchanged. The exported `TelevetEntitlementInterestState` draws the
365
+ paused card too. `confirmNotEntitled` stays in the copy deck and no screen renders it.
366
+ - **Close on an untouched booking leaves without asking.** The auto-matched vet counted as an answer, so every
367
+ booking with a non-empty roster asked "Leave without booking? Your answers will not be saved." of a member who
368
+ had entered nothing. Typed text and a held slot still ask. Both platforms.
369
+ - **A rate-limited televet read settles after one request.** The televet 429 names its wait as
370
+ `retry_after_seconds` in the body; the request funnel read only `resets_at`, so each such 429 was retried up to
371
+ the attempt ceiling against a window that could have minutes left. A numeric `retry_after_seconds` is now the
372
+ retry override, a wait past the retry budget settles on the first 429, and `televet_rate_limited` normalizes to
373
+ `rateLimited`. The visits list shows the server's own sentence for a rate limit instead of "Check your
374
+ connection and try again." (web), or under its title (React Native); no sentence is written by the SDK.
375
+ - The upload progress line no longer reads "Uploading 2 of 1..." while the last file is being confirmed; the
376
+ counter is held to the total, on the card and in the queue's live region.
377
+ - Web surfaces stop a host page's inherited `text-align` at their root. A stock Vite app's `#root` centers text,
378
+ which centered chat replies, the records timeline and the clinic request form. Chat, every records screen, the
379
+ clinic request composer, the booking flow, the visits list, the call and its lobby, the capability off-state
380
+ and the clinical disclaimer set `text-align: start` on their root; your `style` prop still wins.
381
+ - The web full record's two header actions draw the consumer's icons (Feather `send` and `share-2`, as the
382
+ React Native SDK already does) instead of a rotated square and an empty circle. Their accessible names are
383
+ unchanged.
384
+ - The web records entry's size line moves 90_300 -> 94_800 gzip bytes (measured 90_326, plus 5%): main held it
385
+ by 350 bytes and the icons (+290) with the text-align reset and the counter clamp (+86) crossed it by 26.
386
+ Every other entry holds its line on the same build.
387
+
13
388
  ## [0.5.0] - 2026-09-23
14
389
 
15
390
  0.4.1 was tagged but never published to npm: its fixes (the five televet refusal screens and the rest
@@ -0,0 +1,54 @@
1
+ import { ReactNode } from 'react';
2
+ import { T as TelevetConsult } from './bookingTypes-DCs8mmBb.js';
3
+
4
+ /** The calendar entry a booked visit asks the host to write. */
5
+ interface TelevetCalendarEvent {
6
+ /**
7
+ * A stable key for THIS consult's entry.
8
+ *
9
+ * Keyed on the CONSULT so a re-press updates the same entry rather than writing a second event for one
10
+ * visit, and so a later cancel or reschedule can address the SAME row. A host that ignores the key writes
11
+ * duplicates the owner has to clean up by hand.
12
+ */
13
+ readonly key: string;
14
+ readonly title: string;
15
+ readonly startDate: Date;
16
+ readonly endDate: Date;
17
+ /** The reminder offset in minutes, negative for "before". The platform queues its own at the same offset. */
18
+ readonly alarmOffsetMinutes: number;
19
+ }
20
+ /**
21
+ * Writes the entry. Resolves true when it landed in a calendar; false when the host has no calendar to write to
22
+ * or the entry could not be written; `'handed-off'` when the entry was handed to the member to add themselves (a
23
+ * file download, as `downloadTelevetCalendarFile` does), which puts nothing in a calendar yet. On true the
24
+ * confirmation says the visit was added; on `'handed-off'` it keeps Add to Calendar, enabled, so the member can
25
+ * press it again, as the consumer web app's link can be.
26
+ */
27
+ type TelevetCalendarWriter = (event: TelevetCalendarEvent) => Promise<boolean | 'handed-off'>;
28
+ /** The calendar key for a consult. One definition, so a cancel and a reschedule address the booked row. */
29
+ declare function televetConsultCalendarKey(consultId: string): string;
30
+ interface TelevetBookedScreenProps {
31
+ readonly consult: TelevetConsult;
32
+ readonly petName: string | null;
33
+ readonly onDone: () => void;
34
+ /** Absent, there is no Add to calendar control. */
35
+ readonly onAddToCalendar?: TelevetCalendarWriter | undefined;
36
+ /**
37
+ * Has the host PROVEN it can host the call in place? Defaults to false.
38
+ *
39
+ * Fail closed: a host that has not said so is assumed not to have mounted the call surface, and the in-place
40
+ * join promise is withheld rather than printed on a hope.
41
+ */
42
+ readonly canJoinInApp?: boolean;
43
+ /**
44
+ * How long the visit is, in minutes: the booked slot's served length (`televetSlotMinutes`), which is the
45
+ * length the platform runs the visit for. The calendar entry ends this long after the start.
46
+ *
47
+ * Absent, or not a positive number, the entry falls back to `CONSULT_MINUTES`, the same fallback the review's
48
+ * length row uses for a slot that does not say. The prebuilt flow always passes the slot's length.
49
+ */
50
+ readonly lengthMinutes?: number | undefined;
51
+ }
52
+ declare function TelevetBookedScreen({ consult, petName, onDone, onAddToCalendar, canJoinInApp, lengthMinutes, }: TelevetBookedScreenProps): ReactNode;
53
+
54
+ export { type TelevetCalendarWriter as T, type TelevetCalendarEvent as a, TelevetBookedScreen as b, type TelevetBookedScreenProps as c, televetConsultCalendarKey as t };
@@ -0,0 +1,54 @@
1
+ import { ReactNode } from 'react';
2
+ import { T as TelevetConsult } from './bookingTypes-DCs8mmBb.cjs';
3
+
4
+ /** The calendar entry a booked visit asks the host to write. */
5
+ interface TelevetCalendarEvent {
6
+ /**
7
+ * A stable key for THIS consult's entry.
8
+ *
9
+ * Keyed on the CONSULT so a re-press updates the same entry rather than writing a second event for one
10
+ * visit, and so a later cancel or reschedule can address the SAME row. A host that ignores the key writes
11
+ * duplicates the owner has to clean up by hand.
12
+ */
13
+ readonly key: string;
14
+ readonly title: string;
15
+ readonly startDate: Date;
16
+ readonly endDate: Date;
17
+ /** The reminder offset in minutes, negative for "before". The platform queues its own at the same offset. */
18
+ readonly alarmOffsetMinutes: number;
19
+ }
20
+ /**
21
+ * Writes the entry. Resolves true when it landed in a calendar; false when the host has no calendar to write to
22
+ * or the entry could not be written; `'handed-off'` when the entry was handed to the member to add themselves (a
23
+ * file download, as `downloadTelevetCalendarFile` does), which puts nothing in a calendar yet. On true the
24
+ * confirmation says the visit was added; on `'handed-off'` it keeps Add to Calendar, enabled, so the member can
25
+ * press it again, as the consumer web app's link can be.
26
+ */
27
+ type TelevetCalendarWriter = (event: TelevetCalendarEvent) => Promise<boolean | 'handed-off'>;
28
+ /** The calendar key for a consult. One definition, so a cancel and a reschedule address the booked row. */
29
+ declare function televetConsultCalendarKey(consultId: string): string;
30
+ interface TelevetBookedScreenProps {
31
+ readonly consult: TelevetConsult;
32
+ readonly petName: string | null;
33
+ readonly onDone: () => void;
34
+ /** Absent, there is no Add to calendar control. */
35
+ readonly onAddToCalendar?: TelevetCalendarWriter | undefined;
36
+ /**
37
+ * Has the host PROVEN it can host the call in place? Defaults to false.
38
+ *
39
+ * Fail closed: a host that has not said so is assumed not to have mounted the call surface, and the in-place
40
+ * join promise is withheld rather than printed on a hope.
41
+ */
42
+ readonly canJoinInApp?: boolean;
43
+ /**
44
+ * How long the visit is, in minutes: the booked slot's served length (`televetSlotMinutes`), which is the
45
+ * length the platform runs the visit for. The calendar entry ends this long after the start.
46
+ *
47
+ * Absent, or not a positive number, the entry falls back to `CONSULT_MINUTES`, the same fallback the review's
48
+ * length row uses for a slot that does not say. The prebuilt flow always passes the slot's length.
49
+ */
50
+ readonly lengthMinutes?: number | undefined;
51
+ }
52
+ declare function TelevetBookedScreen({ consult, petName, onDone, onAddToCalendar, canJoinInApp, lengthMinutes, }: TelevetBookedScreenProps): ReactNode;
53
+
54
+ export { type TelevetCalendarWriter as T, type TelevetCalendarEvent as a, TelevetBookedScreen as b, type TelevetBookedScreenProps as c, televetConsultCalendarKey as t };
@@ -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-C2EVK416.cjs';
6
- import { W as WidgetPet, P as PetProfileInput } from './petsRepository-DZZoEG3r.cjs';
7
- import { U as UrgencyDisplayLevel, L as LifecyclePort, E as EverfurChatDraftStore } from './attachments-DHvnITuE.cjs';
5
+ import { a as ChatCheckinTarget } from './ports-BSm48zIC.cjs';
6
+ import { W as WidgetPet, P as PetProfileInput } from './petsRepository-B3vr628I.cjs';
7
+ import { U as UrgencyDisplayLevel, L as LifecyclePort, E as EverfurChatDraftStore } from './attachments-CEmT52IA.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-BO3bzTJg.js';
6
- import { W as WidgetPet, P as PetProfileInput } from './petsRepository-BjKJ8UsN.js';
7
- import { U as UrgencyDisplayLevel, L as LifecyclePort, E as EverfurChatDraftStore } from './attachments-C_vtFu2f.js';
5
+ import { a as ChatCheckinTarget } from './ports-DHhxhn95.js';
6
+ import { W as WidgetPet, P as PetProfileInput } from './petsRepository-DdzgINfF.js';
7
+ import { U as UrgencyDisplayLevel, L as LifecyclePort, E as EverfurChatDraftStore } from './attachments-DU1djTNd.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,6 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { StyleProp, ViewStyle } from 'react-native';
3
- import { f as RecordsScreenBaseProps } from './useRecordsDepth-Bnw36jPr.cjs';
3
+ import { f as RecordsScreenBaseProps } from './useRecordsDepth-DRNry1r9.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-Bnw36jPr.cjs';
2
+ import { f as RecordsScreenBaseProps } from './useRecordsDepth-DRNry1r9.cjs';
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-DFF7Faxx.js';
3
+ import { f as RecordsScreenBaseProps } from './useRecordsDepth-DnDhTaUL.js';
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-DFF7Faxx.js';
2
+ import { f as RecordsScreenBaseProps } from './useRecordsDepth-DnDhTaUL.js';
3
3
 
4
4
  interface EverfurRecordsDepthProps extends Pick<RecordsScreenBaseProps, 'petRef' | 'petName' | 'onNavigate' | 'onBack'> {
5
5
  readonly style?: CSSProperties;