@everfur/sdk 0.3.0 → 0.5.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 (187) hide show
  1. package/CHANGELOG.md +251 -4
  2. package/README.md +9 -3
  3. package/dist/{Chat-DwAC2vhB.d.ts → Chat-BOX-03ho.d.ts} +17 -3
  4. package/dist/{Chat-DPUD6dnc.d.cts → Chat-TA-aTcVF.d.cts} +17 -3
  5. package/dist/{DepthViews-DP0R-DhB.d.cts → DepthViews-CgjX2AJ5.d.ts} +1 -1
  6. package/dist/{DepthViews-DbFhR4Du.d.cts → DepthViews-Cviea-ri.d.cts} +1 -1
  7. package/dist/{DepthViews-DNEM96Se.d.ts → DepthViews-D2dRqWRq.d.ts} +1 -1
  8. package/dist/{DepthViews-D_3hl4xk.d.ts → DepthViews-DSFyaSeL.d.cts} +1 -1
  9. package/dist/{ErrorPolicyPort-CwbfmxzJ.d.ts → ErrorPolicyPort-D22LUVrK.d.ts} +1 -1
  10. package/dist/{ErrorPolicyPort-CNf4uQZP.d.cts → ErrorPolicyPort-D6CybiqJ.d.cts} +1 -1
  11. package/dist/{EverfurResult-DN9pL2Ab.d.cts → EverfurError-DmELDpXp.d.cts} +1 -67
  12. package/dist/{EverfurResult-DN9pL2Ab.d.ts → EverfurError-DmELDpXp.d.ts} +1 -67
  13. package/dist/EverfurResult-SGvHKsCB.d.ts +69 -0
  14. package/dist/EverfurResult-ZtH-YV1h.d.cts +69 -0
  15. package/dist/animations/index.cjs +1 -1
  16. package/dist/animations/index.d.cts +6 -5
  17. package/dist/animations/index.d.ts +6 -5
  18. package/dist/animations/index.js +1 -1
  19. package/dist/{attachments-5NeobOCd.d.ts → attachments-C_vtFu2f.d.ts} +7 -5
  20. package/dist/{attachments-D8T5re7e.d.cts → attachments-DHvnITuE.d.cts} +7 -5
  21. package/dist/{attachments-Bw3iMQgu.d.ts → attachments-oD5U2fXI.d.ts} +3 -3
  22. package/dist/{attachments-CQgFrEQ7.d.cts → attachments-pIdGOQfm.d.cts} +3 -3
  23. package/dist/bookingCopy-CJir9mjb.d.ts +1135 -0
  24. package/dist/bookingCopy-DfuA13dh.d.cts +1135 -0
  25. package/dist/branding-Xb_wL8N1.d.cts +212 -0
  26. package/dist/branding-xpQ056sM.d.ts +212 -0
  27. package/dist/callCopy-rDH9JTFo.d.cts +45 -0
  28. package/dist/callCopy-rDH9JTFo.d.ts +45 -0
  29. package/dist/callRoom-7wrE3RP2.d.ts +62 -0
  30. package/dist/callRoom-B_EqaUzx.d.cts +62 -0
  31. package/dist/casesRepository-B-Eg8ZJ3.d.cts +63 -0
  32. package/dist/casesRepository-DHgIYKN9.d.ts +63 -0
  33. package/dist/chat/index.cjs +1 -1
  34. package/dist/chat/index.d.cts +12 -10
  35. package/dist/chat/index.d.ts +12 -10
  36. package/dist/chat/index.js +1 -1
  37. package/dist/client/index.cjs +5 -5
  38. package/dist/client/index.d.cts +15 -11
  39. package/dist/client/index.d.ts +15 -11
  40. package/dist/client/index.js +5 -5
  41. package/dist/{config-CzTZGlfF.d.cts → config-ClI7QxB0.d.ts} +4 -3
  42. package/dist/{config-DW60uRb9.d.ts → config-DAFbf721.d.cts} +4 -3
  43. package/dist/consent/index.cjs +1 -1
  44. package/dist/consent/index.d.cts +9 -8
  45. package/dist/consent/index.d.ts +9 -8
  46. package/dist/consent/index.js +1 -1
  47. package/dist/{context-XvTzq1tE.d.cts → context-B-dUM1Qf.d.cts} +9 -8
  48. package/dist/{context-DXhqHlPU.d.ts → context-CZbgF_OH.d.ts} +9 -8
  49. package/dist/{copy-Mjoo91aS.d.ts → copy-CeRH3NXy.d.ts} +5 -4
  50. package/dist/{copy-bKDNigLb.d.cts → copy-CneuYdrO.d.cts} +5 -4
  51. package/dist/core/index.cjs +9 -9
  52. package/dist/core/index.d.cts +123 -32
  53. package/dist/core/index.d.ts +123 -32
  54. package/dist/core/index.js +9 -9
  55. package/dist/{depth-CErXCU8J.d.cts → depth-B3NFKlF9.d.cts} +38 -6
  56. package/dist/{depth-DjDcJ6kH.d.ts → depth-DO3Ko_qs.d.ts} +38 -6
  57. package/dist/entitlementRepository-74kW5mK0.d.ts +43 -0
  58. package/dist/entitlementRepository-m6zLUmiB.d.cts +43 -0
  59. package/dist/{identity-DI4eVx9S.d.cts → identity-C7uxGwfT.d.cts} +3 -3
  60. package/dist/{identity-BMx2SUOM.d.ts → identity-CLAUq-K4.d.ts} +3 -3
  61. package/dist/index.cjs +6 -6
  62. package/dist/index.d.cts +20 -17
  63. package/dist/index.d.ts +20 -17
  64. package/dist/index.js +6 -6
  65. package/dist/{models-D2EUxPZY.d.cts → models-Bn2z0NNo.d.cts} +1 -1
  66. package/dist/{models-B-Uh2LTf.d.ts → models-DSqA5Ahs.d.ts} +1 -1
  67. package/dist/notifications/index.cjs +2 -2
  68. package/dist/notifications/index.d.cts +5 -4
  69. package/dist/notifications/index.d.ts +5 -4
  70. package/dist/notifications/index.js +2 -2
  71. package/dist/optionalModule-DbJmsq5f.d.cts +21 -0
  72. package/dist/optionalModule-DbJmsq5f.d.ts +21 -0
  73. package/dist/{pets-aB8q0JyX.d.cts → pets-BoB5B7kp.d.cts} +2 -2
  74. package/dist/{pets-CtQdRpWo.d.ts → pets-w4M9d-Dn.d.ts} +2 -2
  75. package/dist/{casesRepository-ClLv6qMl.d.ts → petsRepository-BjKJ8UsN.d.ts} +3 -49
  76. package/dist/{casesRepository-DHwtGRYY.d.cts → petsRepository-DZZoEG3r.d.cts} +3 -49
  77. package/dist/photo/index.cjs +1 -1
  78. package/dist/photo/index.d.cts +3 -2
  79. package/dist/photo/index.d.ts +3 -2
  80. package/dist/photo/index.js +1 -1
  81. package/dist/{ports-DMolTRzU.d.ts → ports-BO3bzTJg.d.ts} +14 -17
  82. package/dist/{ports-DEBzEhbp.d.cts → ports-C2EVK416.d.cts} +14 -17
  83. package/dist/records/depth/index.cjs +2 -2
  84. package/dist/records/depth/index.d.cts +11 -10
  85. package/dist/records/depth/index.d.ts +11 -10
  86. package/dist/records/depth/index.js +2 -2
  87. package/dist/records/index.cjs +3 -3
  88. package/dist/records/index.d.cts +25 -14
  89. package/dist/records/index.d.ts +25 -14
  90. package/dist/records/index.js +3 -3
  91. package/dist/{requestFunnel-CjW7uDuA.d.ts → requestFunnel-D7ipek3e.d.ts} +28 -4
  92. package/dist/{requestFunnel-L-BQfi0z.d.cts → requestFunnel-Djjwgoe9.d.cts} +28 -4
  93. package/dist/{runtime-BR9ysG0J.d.ts → runtimeTypes-CTL3yToK.d.ts} +10 -100
  94. package/dist/{runtime-B8s-_hv8.d.cts → runtimeTypes-D2C9uD85.d.cts} +10 -100
  95. package/dist/server/events/index.cjs +1 -1
  96. package/dist/server/events/index.d.cts +4 -3
  97. package/dist/server/events/index.d.ts +4 -3
  98. package/dist/server/events/index.js +1 -1
  99. package/dist/server/index.cjs +1 -1
  100. package/dist/server/index.d.cts +5 -4
  101. package/dist/server/index.d.ts +5 -4
  102. package/dist/server/index.js +1 -1
  103. package/dist/televet/booking/index.cjs +1 -0
  104. package/dist/televet/booking/index.d.cts +566 -0
  105. package/dist/televet/booking/index.d.ts +566 -0
  106. package/dist/televet/booking/index.js +1 -0
  107. package/dist/televet/call/index.cjs +1 -0
  108. package/dist/televet/call/index.d.cts +161 -0
  109. package/dist/televet/call/index.d.ts +161 -0
  110. package/dist/televet/call/index.js +1 -0
  111. package/dist/televet/index.cjs +1 -1
  112. package/dist/televet/index.d.cts +64 -12
  113. package/dist/televet/index.d.ts +64 -12
  114. package/dist/televet/index.js +1 -1
  115. package/dist/televetGlobal-CEk0_cW9.d.cts +34 -0
  116. package/dist/televetGlobal-Cs9Wbqsy.d.ts +34 -0
  117. package/dist/testing/index.cjs +4 -4
  118. package/dist/testing/index.d.cts +4 -3
  119. package/dist/testing/index.d.ts +4 -3
  120. package/dist/testing/index.js +4 -4
  121. package/dist/testing/rn/index.cjs +1 -1
  122. package/dist/testing/rn/index.d.cts +13 -10
  123. package/dist/testing/rn/index.d.ts +13 -10
  124. package/dist/testing/rn/index.js +1 -1
  125. package/dist/testing/web/index.cjs +1 -1
  126. package/dist/testing/web/index.d.cts +14 -12
  127. package/dist/testing/web/index.d.ts +14 -12
  128. package/dist/testing/web/index.js +1 -1
  129. package/dist/{timelineRows-DZed6dAM.d.ts → timelineRows-7yFNK_rU.d.ts} +1 -1
  130. package/dist/{timelineRows-CaohZzXS.d.cts → timelineRows-CzR1T2DR.d.cts} +1 -1
  131. package/dist/tokens-CIngRkvt.d.cts +362 -0
  132. package/dist/tokens-CIngRkvt.d.ts +362 -0
  133. package/dist/typeStyle-DHJzDaUo.d.cts +112 -0
  134. package/dist/typeStyle-YPS_MXeI.d.ts +112 -0
  135. package/dist/{useRecordsDepth-BHBzJ76R.d.cts → useRecordsDepth-Bnw36jPr.d.cts} +24 -5
  136. package/dist/{useRecordsDepth-BU-OhzH6.d.ts → useRecordsDepth-DFF7Faxx.d.ts} +24 -5
  137. package/dist/{useVetVisit-CjmMa896.d.ts → useVetVisit-BsjoDO-n.d.ts} +21 -6
  138. package/dist/{useVetVisit-Bc84hWUU.d.cts → useVetVisit-Ds8f1peV.d.cts} +21 -6
  139. package/dist/video/index.cjs +1 -1
  140. package/dist/video/index.d.cts +3 -2
  141. package/dist/video/index.d.ts +3 -2
  142. package/dist/video/index.js +1 -1
  143. package/dist/{view-DtSPYpKa.d.ts → view-BqcOYFTr.d.ts} +5 -4
  144. package/dist/{view-C3qPIXGX.d.cts → view-CaJs8WFH.d.cts} +5 -4
  145. package/dist/web/consent/index.cjs +1 -1
  146. package/dist/web/consent/index.d.cts +9 -8
  147. package/dist/web/consent/index.d.ts +9 -8
  148. package/dist/web/consent/index.js +1 -1
  149. package/dist/web/index.cjs +10 -10
  150. package/dist/web/index.d.cts +43 -19
  151. package/dist/web/index.d.ts +43 -19
  152. package/dist/web/index.js +10 -10
  153. package/dist/web/notifications/index.cjs +2 -2
  154. package/dist/web/notifications/index.d.cts +7 -6
  155. package/dist/web/notifications/index.d.ts +7 -6
  156. package/dist/web/notifications/index.js +2 -2
  157. package/dist/web/records/depth/index.cjs +2 -2
  158. package/dist/web/records/depth/index.d.cts +11 -10
  159. package/dist/web/records/depth/index.d.ts +11 -10
  160. package/dist/web/records/depth/index.js +2 -2
  161. package/dist/web/records/index.cjs +3 -3
  162. package/dist/web/records/index.d.cts +123 -14
  163. package/dist/web/records/index.d.ts +123 -14
  164. package/dist/web/records/index.js +3 -3
  165. package/dist/web/televet/booking/index.cjs +2 -0
  166. package/dist/web/televet/booking/index.d.cts +770 -0
  167. package/dist/web/televet/booking/index.d.ts +770 -0
  168. package/dist/web/televet/booking/index.js +2 -0
  169. package/dist/web/televet/call/index.cjs +1 -0
  170. package/dist/web/televet/call/index.d.cts +144 -0
  171. package/dist/web/televet/call/index.d.ts +144 -0
  172. package/dist/web/televet/call/index.js +1 -0
  173. package/dist/web/televet/index.cjs +1 -1
  174. package/dist/web/televet/index.d.cts +61 -9
  175. package/dist/web/televet/index.d.ts +61 -9
  176. package/dist/web/televet/index.js +1 -1
  177. package/package.json +74 -5
  178. package/televet/booking/package.json +8 -0
  179. package/televet/call/package.json +8 -0
  180. package/web/televet/booking/native-blocked.cjs +12 -0
  181. package/web/televet/booking/package.json +8 -0
  182. package/web/televet/call/native-blocked.cjs +12 -0
  183. package/web/televet/call/package.json +8 -0
  184. package/dist/entitlementRepository-DKdXFgTo.d.cts +0 -698
  185. package/dist/entitlementRepository-DSbzuyAW.d.ts +0 -698
  186. package/dist/typeStyle-0TydiZ1w.d.cts +0 -19
  187. package/dist/typeStyle-CWMsoe6b.d.ts +0 -19
@@ -0,0 +1,1135 @@
1
+ import { E as EverfurError } from './EverfurError-DmELDpXp.cjs';
2
+ import { E as EverfurResult } from './EverfurResult-ZtH-YV1h.cjs';
3
+ import { R as RequestFunnel } from './requestFunnel-Djjwgoe9.cjs';
4
+ import { A as AuthContext } from './identity-C7uxGwfT.cjs';
5
+ import { E as ErrorPolicyPort } from './ErrorPolicyPort-D6CybiqJ.cjs';
6
+ import { T as TelemetryPort } from './TelemetryPort-BDNr00hu.cjs';
7
+ import { b as VetCallCredential, c as VetCallJoinRefusal } from './callRoom-B_EqaUzx.cjs';
8
+
9
+ /** The consult lifecycle, as the platform names it. `unknown` for a status this SDK build has not heard of. */
10
+ declare const CONSULT_STATUSES: readonly ["scheduled", "in_progress", "awaiting_summary", "completed", "cancelled_owner", "cancelled_ops", "no_show_owner", "no_show_vet"];
11
+ type ConsultStatus = (typeof CONSULT_STATUSES)[number] | 'unknown';
12
+ /**
13
+ * Statuses after which a consult can never change again.
14
+ *
15
+ * `awaiting_summary` is deliberately NOT terminal: the call is over but the write-up has not landed, and that
16
+ * is precisely the window a summary surface polls through.
17
+ */
18
+ declare const TERMINAL_CONSULT_STATUSES: ReadonlySet<ConsultStatus>;
19
+ declare const CONSULT_KINDS: readonly ["wellness", "prescription"];
20
+ type ConsultKind = (typeof CONSULT_KINDS)[number] | 'unknown';
21
+ /**
22
+ * What a US state permits. TWO INDEPENDENT SERVER FACTS, not one.
23
+ *
24
+ * `wellnessAvailable` and `prescriptionsAvailable` are separate because "we cannot prescribe here but a
25
+ * wellness visit is available" is a real state the booking flow reads, not a copy decision.
26
+ */
27
+ interface TelevetStatePolicy {
28
+ readonly state: string;
29
+ readonly wellnessAvailable: boolean;
30
+ readonly prescriptionsAvailable: boolean;
31
+ /**
32
+ * ALWAYS NULL against a real server, and kept only so a host that reads it still compiles.
33
+ *
34
+ * `StateCapabilitiesResponse` has no `cancellation_policy_text` and never did: it is the jurisdiction
35
+ * matrix, and its own docstring refuses to carry a booking acknowledgement. The terms live on
36
+ * `GET /cancellation-policy` with the version token that makes them reportable, which is what
37
+ * `TelevetCancellationPolicy` and `TelevetBookingController.cancellationPolicy` read. Prefer that; this
38
+ * field is a wire that was never connected, left in place rather than removed because dropping a public
39
+ * field is a release decision, and documented rather than quietly parsed so the next surface does not
40
+ * build a tick on it.
41
+ */
42
+ readonly cancellationPolicyText: string | null;
43
+ }
44
+ /**
45
+ * A clinician card. BIOS ONLY AT LAUNCH, per the platform's own docstring: `photoRef` is an image REF and not a
46
+ * URL, so a surface that has no presign path renders initials rather than a broken image.
47
+ */
48
+ interface TelevetClinician {
49
+ readonly clinicianId: string;
50
+ readonly displayName: string;
51
+ readonly credentials: string | null;
52
+ /** Null below the platform's publication threshold. NEVER coerce to 0: no stars is not zero stars. */
53
+ readonly rating: number | null;
54
+ readonly ratingCount: number | null;
55
+ /** The clinician's own words, or null until they write one. Rendered verbatim, never summarised. */
56
+ readonly bio: string | null;
57
+ /** An image ref, not a URL. Null for a clinician without a photo. */
58
+ readonly photoRef: string | null;
59
+ /** Where they may practise and what they may do there. Empty until served. */
60
+ readonly licensedStates: readonly {
61
+ readonly state: string;
62
+ readonly canPrescribe: boolean;
63
+ }[];
64
+ }
65
+ /** One bookable opening. */
66
+ interface TelevetSlot {
67
+ readonly slotId: string;
68
+ readonly startsAt: string;
69
+ readonly endsAt: string;
70
+ }
71
+ /**
72
+ * An awarded hold. NO HOLD ID: the hold is identified by the slot it sits on plus an expiry, which is why the
73
+ * booking's idempotency key is derived from the SLOT and not from a hold reference that does not exist.
74
+ */
75
+ interface TelevetSlotHold {
76
+ readonly slotId: string;
77
+ readonly expiresAt: string;
78
+ }
79
+ /** A booked consult, as the server created it. What we asked for is the draft; this is what we got. */
80
+ interface TelevetConsult {
81
+ readonly consultId: string;
82
+ readonly petId: string;
83
+ readonly clinicianId: string;
84
+ /** The clinician's name, or null when the server has none. Never invented. */
85
+ readonly clinicianName: string | null;
86
+ /** What the owner wrote at booking, echoed back. */
87
+ readonly reasonText: string | null;
88
+ /** Attachment refs as stored against the consult. */
89
+ readonly mediaRefs: readonly string[];
90
+ readonly status: ConsultStatus;
91
+ readonly kind: ConsultKind;
92
+ readonly scheduledAt: string;
93
+ readonly emergencyFlagged: boolean;
94
+ /**
95
+ * Has the visit's video room been created? Read from the presence of `meeting_url`, which is the same
96
+ * signal the backend's `assert_joinable` checks; the URL itself is NOT kept, because the room credential
97
+ * is minted by the join and a list read is the last place to hold a link to a recorded clinical room.
98
+ * False moments after booking, while the provisioning outbox drains.
99
+ */
100
+ readonly hasRoom: boolean;
101
+ }
102
+ /** The televet entitlement verdict. True only for a live PAID subscription; promo and trial are not enough. */
103
+ interface TelevetBookingEntitlement {
104
+ readonly active: boolean;
105
+ }
106
+ /**
107
+ * The cancellation terms, and the token that says WHICH terms they are.
108
+ *
109
+ * WHY THIS IS ITS OWN READ. `TelevetStatePolicy.cancellationPolicyText` was parsed off the state-policy
110
+ * response, and no such field exists on it: `StateCapabilitiesResponse` carries the jurisdiction matrix and
111
+ * says so in its own docstring, and its `policy_version` is the state matrix's effective date, not the
112
+ * cancellation terms. So the terms tick could never render and there was no version to report, which is why
113
+ * the booking body omitted the attestation pair entirely. The server has always served this on its own route
114
+ * (`GET /cancellation-policy`, CancellationPolicyResponse), unfenced by entitlement precisely so someone
115
+ * deciding whether to book can read what cancelling costs.
116
+ *
117
+ * NEITHER FIELD IS EVER LOCAL. `summary` is the sentence the server derives from the numbers it enforces (the
118
+ * consumer app shipped a placeholder and a wrong hardcoded fee before this route existed), and `version` is
119
+ * evidence of what was on screen: a client-authored token would make the audit row a fiction, and a stale one
120
+ * is refused with a typed 409 rather than recorded as agreement to terms nobody saw.
121
+ */
122
+ interface TelevetCancellationPolicy {
123
+ /** The served sentence, rendered verbatim. Never composed, templated or abbreviated by a client. */
124
+ readonly summary: string;
125
+ /** The server's own version token, reported back in `cancellation_policy_version`. */
126
+ readonly version: string;
127
+ }
128
+ /**
129
+ * The booking request body, in the platform's own field names. Built by `buildBookingBody`, never by hand.
130
+ *
131
+ * THE ATTESTATION PAIR IS BOUND, and the binding is the server's, not a house convention. `BookConsultRequest`
132
+ * declares `state_attested` and `cancellation_policy_version` individually optional and then refuses a request
133
+ * carrying exactly one (`_attestation_evidence_is_whole`, a 422). The compatibility case the pair is optional
134
+ * FOR is a client that predates the review screen and sends neither; a client that renders the tick and the
135
+ * terms and reports half of what the owner did writes an audit row nobody can read. So the SDK models both
136
+ * fields as jointly present or jointly absent and `validateBookingBody` refuses the half pair locally, before
137
+ * it can become a 422 the owner reads as "booking failed".
138
+ */
139
+ interface TelevetBookingBody {
140
+ readonly pet_id: string;
141
+ readonly clinician_id: string;
142
+ readonly slot_id: string;
143
+ readonly state: string;
144
+ readonly reason_text: string;
145
+ readonly species: string | null;
146
+ readonly media_refs: readonly string[];
147
+ readonly emergency_acknowledged: boolean;
148
+ readonly share_health_records: boolean;
149
+ /**
150
+ * The review screen's jurisdiction tick, as evidence.
151
+ *
152
+ * `true` ONLY, never `false`: the server's field is `Literal[True] | None` because persisting an explicit
153
+ * `false` is evidence of nothing and accepting it would let a client "attest false". An owner who has not
154
+ * ticked sends the pair absent, not a `false` half of it.
155
+ */
156
+ readonly state_attested?: true;
157
+ /**
158
+ * Which cancellation terms the owner was SHOWN: the served version token, never a client-authored value.
159
+ *
160
+ * Paired with `state_attested` above. A stale token is refused with a typed 409 rather than recorded as if
161
+ * the owner had read the current terms, which is why it is the SERVER's token and why there is no local
162
+ * default: a build with no version to report sends the pair absent and the audit row says so honestly.
163
+ */
164
+ readonly cancellation_policy_version?: string;
165
+ }
166
+ /** The server's own ceiling on `cancellation_policy_version` (`Field(max_length=64)`). */
167
+ declare const CANCELLATION_POLICY_VERSION_MAX = 64;
168
+ /** The server's own bound on `reason_text`. Enforced locally so an over-long description fails on the screen. */
169
+ declare const REASON_TEXT_MAX = 2000;
170
+ /** The server's own ceiling on `media_refs`. */
171
+ declare const MEDIA_REFS_MAX = 10;
172
+ /** A two-letter US state code, the only shape the booking wire accepts for `state`. */
173
+ declare function isWellFormedStateCode(state: string | null | undefined): boolean;
174
+ /**
175
+ * What cancelling cost, as the server recorded it (`CancelConsultResponse`).
176
+ *
177
+ * `summary` IS THE SERVER'S SENTENCE, rendered verbatim: it is derived from the amount the cancel transaction
178
+ * itself computed, and the backend states the one rule it keeps (a recorded fee is worded as recorded and
179
+ * payable, never as "you will not be charged"). A client-side sentence built from `feeCents` would be a
180
+ * second author for a commercial statement.
181
+ */
182
+ interface TelevetCancelReceipt {
183
+ /** What was recorded against this cancellation, in cents. Zero inside the free window. */
184
+ readonly feeCents: number;
185
+ /** ISO-4217, lowercase. */
186
+ readonly currency: string;
187
+ /** The flag to branch on, rather than comparing `feeCents` to zero or parsing `summary`. */
188
+ readonly feeRecorded: boolean;
189
+ /** The served sentence for the confirmation. Null when the server sent none; never composed here. */
190
+ readonly summary: string | null;
191
+ }
192
+ /** The server's own bound on a cancellation reason (`PartnerCancelRequest.reason`, 1..240). */
193
+ declare const CANCEL_REASON_MAX = 240;
194
+
195
+ /** The partner booking surface, relative to the API base. One prefix, so a dark tenant is dark for all of it. */
196
+ declare const TELEVET_BOOKING_BASE = "/widget/v1/televet";
197
+ /**
198
+ * What taking a slot is addressed to: the slot is IN THE PATH, not in the body.
199
+ *
200
+ * THIS WAS `POST /slots/holds` WITH `{slot_id}` IN THE BODY, and it was invented rather than mirrored. The
201
+ * consumer route the partner plane is being built against is `POST /slots/{slot_id}/hold`
202
+ * (televet_consumer_router.py), a per-slot write whose rate limit and entitlement fence are both keyed on the
203
+ * path. A collection-style hold endpoint does not exist on either plane, so the old shape could never have
204
+ * matched, and nothing said so because the route table was silent about booking (see `knownRoutes.ts`).
205
+ */
206
+ declare function televetHoldCall(slotId: string): string;
207
+ /** What a consult reschedule is addressed to. */
208
+ declare function televetRescheduleCall(consultId: string): string;
209
+ /** One consult of this member's. */
210
+ declare function televetConsultCall(consultId: string): string;
211
+ /** What minting the room credential is addressed to. */
212
+ declare function televetJoinCall(consultId: string): string;
213
+ /** What a cancellation is addressed to. */
214
+ declare function televetCancelCall(consultId: string): string;
215
+ /**
216
+ * How many of the member's consults one read asks for: the route's own maximum.
217
+ *
218
+ * The consumer app's reasoning, carried over (everfur-mobile televetApi.ts `listConsults`): the route defaults
219
+ * to 50, answers a bare list with no cursor to resend, and documents its cursor as opaque, so a client can
220
+ * neither page nor mint one. Asking for the maximum is the honest ceiling until the server returns a cursor.
221
+ */
222
+ declare const TELEVET_CONSULT_PAGE_LIMIT = 100;
223
+ /**
224
+ * The booking port every ported screen reads through.
225
+ *
226
+ * EVERY METHOD IS OPTIONAL except the three writes and the grid, because a host or a test may inject a
227
+ * controller that answers only what its surface needs. A missing method is treated as "that answer is not
228
+ * available", never as an error: `useTelevetBooking` degrades each absence to the same deliberate state a dark
229
+ * route produces.
230
+ */
231
+ interface TelevetBookingController {
232
+ /** What this US state permits. A dark route settles its 404 as an error, never as a refusal. */
233
+ statePolicy?(state: string, signal?: AbortSignal): Promise<EverfurResult<TelevetStatePolicy>>;
234
+ /** Whether this member's visit is covered. A dark route settles its 404 as an error, never as `{active: false}`. */
235
+ entitlement?(signal?: AbortSignal): Promise<EverfurResult<TelevetBookingEntitlement>>;
236
+ /**
237
+ * The cancellation terms and their version token. A dark route answers null, and the review screen then
238
+ * omits the terms tick and books with the attestation pair absent, which is the honest audit row.
239
+ */
240
+ cancellationPolicy?(signal?: AbortSignal): Promise<EverfurResult<TelevetCancellationPolicy | null>>;
241
+ /** Clinicians licensed in a STATE. `state` is required by the route, not a filter: there is no unfiltered roster. */
242
+ clinicians?(state: string, signal?: AbortSignal): Promise<EverfurResult<readonly TelevetClinician[]>>;
243
+ /** This clinician's whole offered window. Never paginated: the range is server-owned and takes no cursor. */
244
+ slots(clinicianId: string, signal?: AbortSignal): Promise<EverfurResult<readonly TelevetSlot[]>>;
245
+ /** The soonest opening across ALL clinicians, or null. Powers the no-slots state and nothing else. */
246
+ nextSlot?(signal?: AbortSignal): Promise<EverfurResult<TelevetSlot | null>>;
247
+ /** Take the slot. The server requires a hold before a booking or a reschedule; it is what resolves the race. */
248
+ holdSlot(slotId: string): Promise<EverfurResult<TelevetSlotHold>>;
249
+ /**
250
+ * Create the consult.
251
+ *
252
+ * `idempotencyKey` is the IDENTITY of this booking, derived once from the held slot when the draft is
253
+ * composed. It seeds the header the funnel writes, so a resubmit after a timeout is the same booking and
254
+ * cannot create a second consult. It is not itself the header: see `book` in the factory below.
255
+ */
256
+ book(body: TelevetBookingBody, idempotencyKey: string): Promise<EverfurResult<TelevetConsult>>;
257
+ /** Move an existing consult to an already-held slot. One server operation, not a book-then-cancel. */
258
+ reschedule(consultId: string, slotId: string): Promise<EverfurResult<TelevetConsult>>;
259
+ /** This member's visits, newest first. A dark route answers an empty list. */
260
+ listConsults?(signal?: AbortSignal): Promise<EverfurResult<readonly TelevetConsult[]>>;
261
+ /** One of this member's visits. A 404 is the opaque "not this member's, or dark" answer, and is an error. */
262
+ getConsult?(consultId: string, signal?: AbortSignal): Promise<EverfurResult<TelevetConsult>>;
263
+ /**
264
+ * Mint the room credential for a visit whose join window is open. ONE ATTEMPT: every mint is a new token
265
+ * and, on the clinician's side, a new arrival, so a retry is the member's to ask for.
266
+ */
267
+ joinConsult?(consultId: string): Promise<EverfurResult<VetCallCredential>>;
268
+ /** Cancel a visit, answering what it cost. `reason` is the member's own words, 1 to 240 characters. */
269
+ cancelConsult?(consultId: string, reason: string): Promise<EverfurResult<TelevetCancelReceipt>>;
270
+ }
271
+ interface TelevetBookingControllerDeps {
272
+ readonly auth: AuthContext;
273
+ /** The partner's `EverfurConfig.errorPolicy`. It can only narrow what the funnel already allows. */
274
+ readonly errorPolicy?: ErrorPolicyPort;
275
+ readonly telemetry?: TelemetryPort;
276
+ /** Injected in tests. Built from `auth` when absent. */
277
+ readonly funnel?: RequestFunnel;
278
+ }
279
+ /**
280
+ * Validate the booking body against the server's own bounds, BEFORE it is sent.
281
+ *
282
+ * The consumer app does this in its reducer (`canConfirm`) and again in its schema; the SDK does it here, in
283
+ * the layer that owns the wire, so a host that drives the controller directly gets the same refusal a screen
284
+ * does rather than a 422 halfway through.
285
+ *
286
+ * THE ATTESTATION PAIR IS CHECKED HERE RATHER THAN LEFT TO THE SERVER because a half pair is the one refusal
287
+ * the owner cannot act on. Every other rule above names something they can fix (pick a pet, write a reason);
288
+ * a 422 for "you sent the tick without the terms version" is a client bug wearing a booking failure, and it
289
+ * arrives after they pressed Confirm. Refusing locally keeps it a programming error, where it belongs.
290
+ */
291
+ declare function validateBookingBody(body: TelevetBookingBody): string | null;
292
+ declare function createTelevetBookingController(deps: TelevetBookingControllerDeps): Required<TelevetBookingController>;
293
+
294
+ /**
295
+ * The classifier's answer about one piece of free text.
296
+ *
297
+ * TWO FIELDS, AND `fallback` IS NOT A FAILURE. The consumer treats "the classifier could not give a verdict"
298
+ * as an emergency for this purpose, because the booking route refuses it the same way
299
+ * (useEarlyTriage.ts:70: `verdict.isEmergency || verdict.fallback`). A read that never completed is a
300
+ * different thing and is handled by the absence of a verdict, not by a field on one.
301
+ */
302
+ interface TelevetTriageVerdict {
303
+ readonly isEmergency: boolean;
304
+ /** The classifier ran and declined to decide. Refused like an emergency, by the booking route and here. */
305
+ readonly fallback: boolean;
306
+ }
307
+ /**
308
+ * The seam the triage read arrives through.
309
+ *
310
+ * A PROMISE THAT RESOLVES TO NULL, NEVER A REJECTION THE CALLER HAS TO CATCH: null means "no verdict" (the
311
+ * route is dark, the request failed, the owner left), which this hook treats as "go on". A host or the
312
+ * controller supplies it; absent, no question is asked.
313
+ */
314
+ type TelevetTriageRead = (reasonText: string, signal: AbortSignal) => Promise<TelevetTriageVerdict | null>;
315
+ interface TelevetEarlyTriage {
316
+ /** The warning is up. While true the flow must not advance past Describe. */
317
+ readonly open: boolean;
318
+ /** A read is in flight. The Continue control shows it rather than appearing dead. */
319
+ readonly checking: boolean;
320
+ /** True when the flow should go on; false when it must not move (a warning opened, or a read is running). */
321
+ check(reasonText: string): Promise<boolean>;
322
+ /** "I have handled this, keep booking", recorded against the EXACT words it was answered for. */
323
+ acknowledge(reasonText: string): void;
324
+ /** Back from the warning: closes it without recording an acknowledgement. */
325
+ close(): void;
326
+ /** Abort an in-flight read (unmount, or leaving the step). */
327
+ cancel(): void;
328
+ /** A different pet is a different booking: its words are asked about afresh. */
329
+ reset(): void;
330
+ /** Whether these exact words carry an acknowledgement, so the booking body can say so truthfully. */
331
+ isAcknowledged(reasonText: string): boolean;
332
+ }
333
+ /**
334
+ * The hook. `read` absent (a dark tenant, or a host that wired no seam) means `check` always answers "go on",
335
+ * which is the same behaviour as a read that failed and is deliberately indistinguishable from it.
336
+ */
337
+ declare function useEarlyTriage(read: TelevetTriageRead | null | undefined): TelevetEarlyTriage;
338
+
339
+ /**
340
+ * The server's own demand bucket. A CLOSED SET, and callers may not substitute free text.
341
+ *
342
+ * THE TWO ARE CLOSED BY DIFFERENT FIXES, which is why the server distinguishes them and why a call site that
343
+ * guesses is wrong in a way nobody can see: `no_licensed_vet` is closed by licensing a clinician in that
344
+ * state, `no_prescribing` by a change in what that state permits remotely. The consumer container is explicit
345
+ * about picking the first for an empty roster and for a blocked state
346
+ * (TelevetBookingContainer.tsx, the `no_licensed_vet, not no_prescribing` comment).
347
+ */
348
+ type TelevetStateInterestReason = 'no_licensed_vet' | 'no_prescribing';
349
+ /** What the control needs to render its lifecycle. Null means there is nothing to render; see the header. */
350
+ interface TelevetStateInterestAction {
351
+ /** The write SUCCEEDED in this session. Never inferred, never optimistic. */
352
+ readonly joined: boolean;
353
+ readonly pending: boolean;
354
+ readonly failed: boolean;
355
+ onPress(): void;
356
+ }
357
+ /**
358
+ * The seam the write goes through. Resolves true on a recorded interest and false otherwise; a rejection is
359
+ * caught here and read as false, so a host cannot turn a throw into a silent success.
360
+ */
361
+ type TelevetStateInterestWrite = (state: string, reason: TelevetStateInterestReason) => Promise<boolean>;
362
+ /**
363
+ * Local, per-mounted-booking feedback for the owner-initiated state-interest write.
364
+ *
365
+ * LOCAL SUCCESS IS DELIBERATELY EPHEMERAL, verbatim from the consumer's reasoning: the server is idempotent,
366
+ * so this only prevents repeat taps in this session and "does not pretend we know the owner's durable
367
+ * subscription state" (useBookingStateInterest.ts:20-25). Nothing here is persisted, because a remembered
368
+ * "you are on the list" that outlived a state change would be a claim this client cannot stand behind.
369
+ */
370
+ declare function useStateInterest(state: string | null | undefined, write: TelevetStateInterestWrite | null | undefined): {
371
+ actionFor(reason: TelevetStateInterestReason): TelevetStateInterestAction | null;
372
+ };
373
+
374
+ /** Why the roster is empty. Closed, because each value picks a different true sentence. */
375
+ type TelevetEmptyRosterCause =
376
+ /** A real, state-scoped read answered a literal empty list. A licensure gap. */
377
+ 'roster'
378
+ /** The route is dark for this tenant, so no read happened. Not a licensure fact. */
379
+ | 'dark';
380
+ /** What the empty-roster screen renders. Derived, never chosen at a call site. */
381
+ interface TelevetEmptyRosterView {
382
+ readonly title: string;
383
+ readonly message: string;
384
+ /**
385
+ * The retry label, or null when a retry cannot help.
386
+ *
387
+ * NULL IS LOAD BEARING and is not a styling preference. `TelevetStateCard` renders its action only when a
388
+ * label AND a handler are both present, so a null label removes the control rather than disabling it. A
389
+ * `Check again` on a licensure gap is a button whose every press repeats the same answer.
390
+ */
391
+ readonly retryLabel: string | null;
392
+ /**
393
+ * Which demand bucket a state-interest write would carry from this screen, or null when interest cannot be
394
+ * offered here.
395
+ *
396
+ * `no_licensed_vet`, NOT `no_prescribing`, and only on the licensure cause. The server treats the two as
397
+ * distinct fixes; this one is closed by licensing a clinician in the state, which is exactly what an empty
398
+ * state-scoped roster reports. Offering interest on the DARK cause would record a demand signal for a
399
+ * state nobody established anything about.
400
+ */
401
+ readonly interestReason: 'no_licensed_vet' | null;
402
+ }
403
+ /**
404
+ * The view for an empty roster, from the cause.
405
+ *
406
+ * A PURE FUNCTION over the copy deck, so both platform ports read the same three fields and neither can pick
407
+ * its own sentence. It takes the CAUSE rather than a boolean, because a boolean at a call site is how the
408
+ * dark tenant and the licensure gap got the same words in the first place.
409
+ */
410
+ declare function televetEmptyRosterView(cause: TelevetEmptyRosterCause): TelevetEmptyRosterView;
411
+ /**
412
+ * The cause, from what the flow knows.
413
+ *
414
+ * `surfaceServed` IS THE CALLER'S ONE FACT: has anything on this booking surface answered a real response?
415
+ * The container derives it from the reads it has already made, and it is deliberately NOT re-derived here
416
+ * from a 404, because `clinicians` has already swallowed that into `ok([])` by the time a screen sees it.
417
+ * That swallow is the right call for rendering and the wrong one for this decision, which is exactly why
418
+ * the fact has to be carried alongside the empty list rather than recovered from it.
419
+ */
420
+ declare function televetEmptyRosterCause(surfaceServed: boolean): TelevetEmptyRosterCause;
421
+
422
+ /**
423
+ * Why a pick was refused before it was ever sent.
424
+ *
425
+ * THE CONSUMER'S OWN THREE BUCKETS, not a set invented here: mobile's `ConsultAttachmentOutcome.refused` and
426
+ * the web app's `ConsultAttachmentRefusal` both carry exactly `size`, `type` and `unreadable`, and each has a
427
+ * ported sentence. A fourth cause with no consumer sentence would have nothing to say, so the type is closed.
428
+ */
429
+ type TelevetMediaRefusal = 'size' | 'type' | 'unreadable';
430
+ /** Counts of what did not reach the vet, by cause. The consumer web app's `ConsultAttachmentShortfall`. */
431
+ interface TelevetMediaShortfall {
432
+ /** Passed the rules, then the send did not carry it. On this surface that is every pick; see the header. */
433
+ readonly failed: number;
434
+ readonly refused: Readonly<Record<TelevetMediaRefusal, number>>;
435
+ }
436
+ /** Nothing to say. Frozen and shared so a caller cannot mutate the empty case into a false report. */
437
+ declare const NO_MEDIA_SHORTFALL: TelevetMediaShortfall;
438
+ /** True when there is literally nothing to tell the owner. */
439
+ declare function isMediaShortfallEmpty(shortfall: TelevetMediaShortfall): boolean;
440
+ /**
441
+ * One sentence per cause, in the consumer's order, or an empty list when there is nothing to say.
442
+ *
443
+ * THE ORDER IS THE CONSUMER'S AND IS NOT ALPHABETICAL: dropped, then size, then type, then unreadable. Both
444
+ * consumer apps list them that way (MediaDropNotice.tsx:31-36 and consultAttachmentUpload.ts:385-403), which
445
+ * puts the thing that got furthest through the flow first.
446
+ *
447
+ * A NEGATIVE OR NON-FINITE COUNT IS TREATED AS ZERO rather than trusted. These numbers can arrive from a
448
+ * host's own upload seam, and `{n}` is interpolated straight into a sentence an owner reads: "-1 attachment
449
+ * did not upload." is a defect wearing a report. It is floored rather than thrown on, because failing a
450
+ * booking confirmation over a bad count would cost the owner the one screen that tells them anything.
451
+ */
452
+ declare function televetMediaShortfallLines(shortfall: TelevetMediaShortfall): readonly string[];
453
+ /**
454
+ * The shortfall THIS SURFACE actually has: every pick the owner made, reported as not sent.
455
+ *
456
+ * WHY THIS FUNCTION EXISTS AT ALL, and why it is not a stub for a future upload. The consumer apps derive
457
+ * their shortfall from an upload RESULT. The SDK has no upload: `media_refs` leaves as `[]` whatever the
458
+ * owner picked, and that is a decision with a written reason (no confirmed ref format, no read path, no
459
+ * attachment purpose on the partner plane), not a gap about to close. So the honest count is knowable
460
+ * without a network call: it is the length of the tray.
461
+ *
462
+ * `failed`, NOT A REFUSAL BUCKET. The picks passed every local rule - the row accepted them, capped them at
463
+ * `MEDIA_REFS_MAX` and showed them as thumbnails. Nothing about their size, type or readability is why they
464
+ * did not arrive. "{n} attachments did not upload." is the true sentence; "too large to send" would be a
465
+ * false explanation, and a false explanation invites the owner to re-pick a smaller photo that will be
466
+ * dropped in exactly the same way.
467
+ *
468
+ * IT REPORTS ZERO WHEN THE TRAY IS EMPTY, so the notice mounts unconditionally and renders nothing on the
469
+ * common path, which is the property the consumer component was built around (MediaDropNotice.tsx:19-20).
470
+ */
471
+ declare function mediaShortfallFromUnsentPicks(pickCount: number): TelevetMediaShortfall;
472
+ /**
473
+ * Add a refusal the ATTACHMENT ROW itself made, so a silently discarded pick is stated.
474
+ *
475
+ * THE ROW ALREADY DISCARDS PICKS AND SAID NOTHING. `IntakeMedia` drops a pick whose uri is already attached
476
+ * and then `.slice(0, MEDIA_REFS_MAX)` cuts whatever is past the cap. Both are correct behaviours and both
477
+ * were silent, so an owner who selected eight photos into a tray with three slots left saw five of them
478
+ * vanish with no explanation. The consumer mobile app's cap refusal is `size`; this one is neither a size
479
+ * nor a type nor an unreadable file, so it is reported as `failed` for the same reason the unsent picks are:
480
+ * it is the only one of the four sentences that is TRUE, and a true vague sentence beats a false precise one.
481
+ *
482
+ * Returns a new object; the input is never mutated (house rule, and these are frozen anyway).
483
+ */
484
+ declare function withUnsentPicks(shortfall: TelevetMediaShortfall, pickCount: number): TelevetMediaShortfall;
485
+
486
+ type TelevetBookingStep = 'pet' | 'issues' | 'clinician' | 'time' | 'reason' | 'review' | 'booked';
487
+ /**
488
+ * Ordered for the progress rail. `booked` is terminal and counts toward nothing: the flow is over, and
489
+ * including it would show a rail at 6/7 on a screen with no next step.
490
+ */
491
+ declare const TELEVET_BOOKING_STEPS: readonly TelevetBookingStep[];
492
+ interface TelevetBookingDraft {
493
+ readonly petId: string | null;
494
+ /** Two letters, re-confirmed on EVERY booking: the server pattern demands it. */
495
+ readonly state: string | null;
496
+ readonly clinicianId: string | null;
497
+ /** The held slot. A hold has no id of its own on this wire. */
498
+ readonly slot: TelevetSlot | null;
499
+ readonly holdExpiresAt: string | null;
500
+ /** The consent tick that mints a records grant in the booking transaction. */
501
+ readonly shareHealthRecords: boolean;
502
+ /**
503
+ * Minted ONCE per composed booking and reused across retries.
504
+ *
505
+ * Derived from the SLOT rather than a hold id, which this wire does not have. A timeout followed by a retry
506
+ * must not create a second consult.
507
+ */
508
+ readonly idempotencyKey: string | null;
509
+ }
510
+ declare const emptyTelevetDraft: TelevetBookingDraft;
511
+ interface TelevetBookingContext {
512
+ /** Skips the picker for a single-pet household. */
513
+ readonly ownedPetCount: number;
514
+ /** Shows the issues step. The web flow sets it, mirroring the consumer web funnel; absent, it is skipped. */
515
+ readonly withIssues?: boolean;
516
+ }
517
+ interface TelevetBookingState {
518
+ readonly index: number;
519
+ readonly draft: TelevetBookingDraft;
520
+ /** The furthest step reached, so the rail never appears to go backwards. */
521
+ readonly furthestIndex: number;
522
+ }
523
+ /**
524
+ * The starting state, OPENED ON THE FIRST VISIBLE STEP.
525
+ *
526
+ * Index 0 is `pet`, which a one-pet household skips, so starting there opens the flow on a screen that renders
527
+ * nothing and never advances. The skip rule must be applied at every place an index is chosen, not only when
528
+ * stepping.
529
+ *
530
+ * `ctx` is required rather than optional: an initial state that does not know the household size cannot know
531
+ * where to start. The seeded draft matters too - a host that seeds `state` (and, for a one-pet household,
532
+ * `petId`) must open PAST both of those steps.
533
+ */
534
+ declare function initialTelevetBookingState(ctx: TelevetBookingContext, seed?: Partial<TelevetBookingDraft>): TelevetBookingState;
535
+ type TelevetBookingAction = {
536
+ readonly type: 'patch';
537
+ readonly patch: Partial<TelevetBookingDraft>;
538
+ } | {
539
+ readonly type: 'next';
540
+ } | {
541
+ readonly type: 'back';
542
+ } | {
543
+ readonly type: 'goTo';
544
+ readonly step: TelevetBookingStep;
545
+ };
546
+ /**
547
+ * Whether a step is skipped for STRUCTURAL reasons rather than user choice.
548
+ *
549
+ * NO DRAFT PARAMETER. The consumer's version takes one because an earlier shape skipped a `state` step once
550
+ * the draft held a code; that step is gone, so a parameter nothing reads would only invite a future skip rule
551
+ * that depends on draft contents without the attestation that made the old one safe.
552
+ *
553
+ * A skipped step must still have its value SET. The worst defect a flow of this shape can carry is skipping
554
+ * the pet picker for a one-pet household and never writing `petId`, which dead-ends the majority case; the
555
+ * flow seeds it instead.
556
+ */
557
+ declare function isSkipped(step: TelevetBookingStep, ctx: TelevetBookingContext): boolean;
558
+ /**
559
+ * Is there a visible step behind this one?
560
+ *
561
+ * EXPORTED because a host deciding with `state.index > 0` is answering a different question: a one-pet
562
+ * household starts at index 1 (its `pet` step is skipped), so that test says "yes" on the FIRST screen and
563
+ * produces a Back that dispatches, finds nothing behind it, and leaves the index unchanged - a dead press.
564
+ */
565
+ declare function hasPreviousStep(state: {
566
+ readonly index: number;
567
+ }, ctx: TelevetBookingContext): boolean;
568
+ declare function televetBookingReducer(state: TelevetBookingState, action: TelevetBookingAction, ctx: TelevetBookingContext): TelevetBookingState;
569
+ /** The step currently on screen. */
570
+ declare function currentStep(state: TelevetBookingState): TelevetBookingStep;
571
+ /**
572
+ * Is the draft complete enough to book?
573
+ *
574
+ * Mirrors the wire's own required fields, so an incomplete draft fails HERE rather than as a 422 after the
575
+ * owner presses Confirm.
576
+ */
577
+ declare function canConfirm(draft: TelevetBookingDraft, reasonText: string): boolean;
578
+ /**
579
+ * Has the hold expired?
580
+ *
581
+ * `nowMs` is INJECTED so the screen, the countdown and the confirm all answer to one clock rather than three
582
+ * calls to `Date.now()`.
583
+ */
584
+ declare function isHoldExpired(draft: TelevetBookingDraft, nowMs: number): boolean;
585
+ /**
586
+ * The idempotency key for a composed booking, derived from the SLOT.
587
+ *
588
+ * One definition, because the reducer clears it and the flow mints it: two spellings would let a retry after a
589
+ * cleared draft create a second consult.
590
+ */
591
+ declare function televetBookingKey(slotId: string): string;
592
+ /**
593
+ * The attestation half of the booking body: both fields, or nothing.
594
+ *
595
+ * ONE DEFINITION FOR BOTH PLATFORMS, and it lives here rather than in either container, because "did the owner
596
+ * attest" is a view DECISION made from the same three inputs on RN and on web, and two spellings of a bound
597
+ * pair is how one of them ships a 422. The server refuses exactly one of the two
598
+ * (`_attestation_evidence_is_whole`), so this returns a whole pair or an empty object and never a half.
599
+ *
600
+ * THREE CONDITIONS, ALL REQUIRED, and each one is a distinct fact rather than a redundant check:
601
+ * - `stateAttested` the owner ticked the jurisdiction. Evidence.
602
+ * - `termsAttested` the owner ticked the terms they were shown. Evidence.
603
+ * - `policy` terms with a SERVED version token were actually on screen. Without it there is nothing
604
+ * to name, and naming the wrong token is a typed 409 or, worse, an audit row that cites
605
+ * terms nobody presented.
606
+ *
607
+ * An owner shown no terms row has nothing to tick and books with the pair absent, which is exactly the
608
+ * compatibility case the server's optional fields exist for.
609
+ */
610
+ declare function attestation(stateAttested: boolean, termsAttested: boolean, policy: {
611
+ readonly version: string;
612
+ } | null | undefined): {
613
+ readonly state_attested: true;
614
+ readonly cancellation_policy_version: string;
615
+ } | Record<string, never>;
616
+
617
+ /**
618
+ * Why a booking did not complete.
619
+ *
620
+ * DECLARED HERE because this module is what produces every value, and both platform containers re-export it
621
+ * as their own `TelevetBookingFailure` so a host's existing import keeps working. `not_entitled` is separate
622
+ * from `failed` because the two need opposite affordances: a transient failure invites a retry and a 402
623
+ * must not. `unavailable` is separate from both because no retry can succeed.
624
+ */
625
+ type TelevetBookingFailure = 'taken' | 'expired' | 'failed' | 'not_entitled'
626
+ /** The remedy is a DIFFERENT VET, not a different time: re-picking a slot with the same clinician refuses again. */
627
+ | 'clinician_unavailable'
628
+ /** Per-owner caps. The remedy is WAITING, so this must never render as an invitation to press again. */
629
+ | 'rate_limited'
630
+ /** The partner booking surface is dark here. No retry can fix it, so the flow says so and offers its exit. */
631
+ | 'unavailable' | null;
632
+
633
+ /** How early the join arms, in minutes before `scheduled_at`. Both consumer apps: 15. */
634
+ declare const TELEVET_JOIN_OPENS_MINUTES_BEFORE = 15;
635
+ /** How late the join stays armed, in minutes after `scheduled_at`: 10 minute consult plus a 4 hour grace. */
636
+ declare const TELEVET_JOIN_CLOSES_MINUTES_AFTER = 250;
637
+ /** When the join window opens, as epoch milliseconds; null for an unreadable time. */
638
+ declare function televetJoinOpensAtMs(consult: Pick<TelevetConsult, 'scheduledAt'>): number | null;
639
+ /** The mobile readiness: ready, or why not. */
640
+ type TelevetJoinReadiness = {
641
+ readonly ready: true;
642
+ } | {
643
+ readonly ready: false;
644
+ readonly reason: 'not_open_yet' | 'consult_over';
645
+ };
646
+ /**
647
+ * May the member press Join now? The consumer MOBILE rule, verbatim in order.
648
+ *
649
+ * `in_progress` BEATS THE CLOCK: the server is asserting the call is happening, and refusing on our own
650
+ * arithmetic would lock the member out of a call their vet is sitting in. AN UNREADABLE TIME DOES NOT ARM THE
651
+ * BUTTON: opening a room for a visit whose time we cannot read is worse than a stated "not yet".
652
+ */
653
+ declare function televetJoinReadiness(consult: TelevetConsult | null, nowMs: number): TelevetJoinReadiness;
654
+ /**
655
+ * Why the web row's Join is dark, in the backend's own vocabulary (`assert_joinable`'s 409 `detail.reason`):
656
+ * `status_{status}` for a visit that is not scheduled or in progress, `room_not_ready` for a visit whose
657
+ * video room has not been created yet, and the two window edges.
658
+ */
659
+ type TelevetJoinBlock = 'too_early' | 'too_late' | 'room_not_ready' | `status_${string}`;
660
+ type TelevetJoinDecision = {
661
+ readonly joinable: true;
662
+ } | {
663
+ readonly joinable: false;
664
+ readonly reason: TelevetJoinBlock;
665
+ };
666
+ /**
667
+ * May the member press Join now? The consumer WEB rule, which mirrors the backend's order: STATUS FIRST, so a
668
+ * cancelled visit reads as cancelled rather than as "too late"; THE ROOM BEFORE THE WINDOW, so a member who
669
+ * opens the page moments after booking hears that the room is being set up rather than that they are early.
670
+ */
671
+ declare function televetJoinDecision(consult: TelevetConsult, nowMs: number): TelevetJoinDecision;
672
+ /**
673
+ * The visits, split into what is still ahead and what is over. Both consumer apps' `splitConsults` /
674
+ * `splitVisits`, which agree.
675
+ *
676
+ * A visit is UPCOMING while its status is one the member could still join AND its join window has not
677
+ * closed: the same two facts the Join control reads, so a row can never be joinable and filed under the past
678
+ * at once. `awaiting_summary` is not upcoming: the call has ended. Upcoming is soonest first, because the visit
679
+ * that needs the member next belongs at the top; the past is newest first. An unreadable time sinks to the
680
+ * past rather than corrupting the sort.
681
+ */
682
+ declare function splitTelevetVisits(consults: readonly TelevetConsult[], nowMs: number): {
683
+ readonly upcoming: readonly TelevetConsult[];
684
+ readonly past: readonly TelevetConsult[];
685
+ };
686
+ /**
687
+ * May the member move or cancel this visit? Only while it is SCHEDULED, which is the backend writer's own
688
+ * lock (`_lock_scheduled`) and both consumer apps' predicate. `awaiting_summary` is non-terminal only because
689
+ * the clinician may still write the record; offering Cancel there invites a refusal for a visit that happened.
690
+ */
691
+ declare function canManageTelevetVisit(status: ConsultStatus): boolean;
692
+
693
+ /** One read, in the shape the ported screens branch on. */
694
+ interface TelevetRead<T> {
695
+ /** False when the question cannot be asked yet. A disabled read is never pending and never errors. */
696
+ readonly enabled: boolean;
697
+ readonly data: T | null;
698
+ readonly isPending: boolean;
699
+ readonly isError: boolean;
700
+ readonly error: EverfurError | null;
701
+ refetch(): void;
702
+ }
703
+ /** One write, in the shape the ported screens branch on. */
704
+ interface TelevetWrite<Args extends readonly unknown[], T> {
705
+ readonly isPending: boolean;
706
+ /** The argument of the in-flight call, so a grid can mark the one row it is acting on. Null when idle. */
707
+ readonly variables: Args[0] | null;
708
+ mutate(...args: [...Args, {
709
+ readonly onSuccess?: (value: T) => void;
710
+ readonly onError?: (error: EverfurError) => void;
711
+ }?]): void;
712
+ }
713
+ /**
714
+ * The booking controller for the current scope: the injected one, or one built from the provider's session.
715
+ *
716
+ * `scopeEpoch` is a real dependency, exactly as it is for `useVetVisit`: an anonymous logout changes no
717
+ * identity string but rotates the session, and a controller that kept the previous session's auth would book a
718
+ * visit for the previous user.
719
+ */
720
+ declare function useTelevetBookingController(injected?: TelevetBookingController): TelevetBookingController | null;
721
+ /**
722
+ * True only when the server grants `televet` AND the provider is scoped to a user.
723
+ *
724
+ * The same predicate `useVetVisit` uses, so the button and the in-app flow cannot disagree about whether this
725
+ * member can have a visit at all.
726
+ */
727
+ declare function useTelevetBookingAvailable(): {
728
+ readonly available: boolean;
729
+ readonly isPending: boolean;
730
+ };
731
+ /** What this US state permits. Disabled until a well-formed two-letter code is known. */
732
+ declare function useTelevetStatePolicy(state: string | null, controller: TelevetBookingController | null): TelevetRead<TelevetStatePolicy>;
733
+ /**
734
+ * Whether this member's plan covers a video visit.
735
+ *
736
+ * UNCONDITIONAL AND PARAMETERLESS, so it starts in parallel with the state policy rather than after it.
737
+ * Gating it behind a step or a chosen state would recreate the waterfall the gate exists to remove: the answer
738
+ * does not depend on anything the owner has filled in yet.
739
+ */
740
+ declare function useTelevetBookingEntitlement(controller: TelevetBookingController | null): TelevetRead<{
741
+ readonly active: boolean;
742
+ }>;
743
+ /**
744
+ * The cancellation terms and their version token.
745
+ *
746
+ * UNCONDITIONAL, like the entitlement: the terms do not depend on the state, the vet or the slot, and the
747
+ * review screen needs them the moment it renders. Reading them on entry to the review step would show the
748
+ * owner a Confirm button whose terms row appears a beat later.
749
+ *
750
+ * A CONTROLLER WITHOUT THE METHOD reads as no terms, which is the same deliberate off-state a dark route
751
+ * produces: the tick is omitted and the booking sends the attestation pair absent, rather than a tick the
752
+ * SDK could not report.
753
+ */
754
+ declare function useTelevetCancellationPolicy(controller: TelevetBookingController | null): TelevetRead<TelevetCancellationPolicy | null>;
755
+ /** Clinicians licensed in a STATE. `state` is the route's required parameter, not a filter. */
756
+ declare function useTelevetClinicians(state: string | null, controller: TelevetBookingController | null): TelevetRead<readonly TelevetClinician[]>;
757
+ /** This clinician's whole offered window. Disabled until one is chosen. */
758
+ declare function useTelevetSlots(clinicianId: string | null, controller: TelevetBookingController | null): TelevetRead<readonly TelevetSlot[]>;
759
+ /**
760
+ * The soonest opening across ALL clinicians, for the no-slots state only.
761
+ *
762
+ * Read UNCONDITIONALLY rather than lazily on the empty case: it is a cheap cross-clinician lookup, and
763
+ * fetching it only after discovering the grid is empty would make the useful half of that state arrive a
764
+ * round-trip after the disappointing half.
765
+ */
766
+ declare function useTelevetNextSlot(controller: TelevetBookingController | null): TelevetRead<TelevetSlot | null>;
767
+ /** Take the slot. The server requires a hold before a booking or a reschedule. */
768
+ declare function useTelevetHoldSlot(controller: TelevetBookingController | null): TelevetWrite<[slotId: string], TelevetSlotHold>;
769
+ /** Create the consult. */
770
+ declare function useTelevetBookConsult(controller: TelevetBookingController | null): TelevetWrite<[body: TelevetBookingBody, idempotencyKey: string], TelevetConsult>;
771
+ /** Move an existing consult to an already-held slot. */
772
+ declare function useTelevetRescheduleConsult(consultId: string | null, controller: TelevetBookingController | null): TelevetWrite<[slotId: string], TelevetConsult>;
773
+
774
+ /** Where a join is. `reason` is the server's own refusal reason, for a surface that words each one. */
775
+ type TelevetJoinState = {
776
+ readonly status: 'joining';
777
+ } | {
778
+ readonly status: 'ready';
779
+ readonly credential: VetCallCredential;
780
+ } | {
781
+ readonly status: 'refused';
782
+ readonly refusal: VetCallJoinRefusal;
783
+ readonly reason: string | null;
784
+ };
785
+ /** Mint the room credential for `consultId` on mount; `retry` asks again. */
786
+ declare function useTelevetJoin(consultId: string, controller: TelevetBookingController | null): {
787
+ readonly state: TelevetJoinState;
788
+ readonly retry: () => void;
789
+ };
790
+
791
+ type TelevetBookingRefusal =
792
+ /** Triage flagged an emergency. Carries `categories`. MUST show the warning. */
793
+ {
794
+ readonly kind: 'emergency';
795
+ readonly categories: readonly string[];
796
+ }
797
+ /** No live paid subscription (402). Deterministic: never invite a retry. */
798
+ | {
799
+ readonly kind: 'not_entitled';
800
+ }
801
+ /** The slot went, or the hold lapsed. Re-fetch the grid. */
802
+ | {
803
+ readonly kind: 'slot_taken';
804
+ }
805
+ /** That clinician cannot host this consult. Pick another; a different TIME refuses again. */
806
+ | {
807
+ readonly kind: 'clinician_unavailable';
808
+ }
809
+ /**
810
+ * Too many attempts. Distinct from `failed` because the remedy is WAITING, and `failed` invites an immediate
811
+ * retry that fails again and, on a fixed window, can push the reset further out. Carries the server's own
812
+ * wait hint when it sends one.
813
+ */
814
+ | {
815
+ readonly kind: 'rate_limited';
816
+ readonly retryAfterSeconds: number | null;
817
+ /**
818
+ * The SERVER'S OWN wording, when it sends any.
819
+ *
820
+ * Carried rather than replaced with a string written here: a wait message is the server's to phrase, it
821
+ * can change without an app release, and this SDK does not author user-facing copy. Null renders no line
822
+ * at all rather than a placeholder.
823
+ */
824
+ readonly message: string | null;
825
+ }
826
+ /**
827
+ * The partner booking surface is not served for this tenant (404), so no request on it can succeed.
828
+ *
829
+ * NOT IN THE CONSUMER'S UNION, and it is the one member the port adds. The consumer app talks to its own
830
+ * consumer surface, which is either on or dark for everybody; a PARTNER integration can have vet visits
831
+ * granted and the in-app booking routes withheld, and no retry fixes that: the flow shows its unavailable
832
+ * state, inside the partner app. Folding it into `failed` would offer a retry that can never work.
833
+ */
834
+ | {
835
+ readonly kind: 'unavailable';
836
+ }
837
+ /** Anything else, including a transport failure. A retry is reasonable. */
838
+ | {
839
+ readonly kind: 'failed';
840
+ };
841
+ declare function televetBookingRefusal(error: EverfurError): TelevetBookingRefusal;
842
+
843
+ /** The platform-fixed consult length. */
844
+ declare const CONSULT_MINUTES = 10;
845
+ /**
846
+ * "9:20 PM - 9:30 PM EDT", the visit's bracket, zone-qualified.
847
+ *
848
+ * THE END TIME IS DERIVED, not served: a consult response carries `scheduled_at` only, and the platform fixes
849
+ * the consult at `CONSULT_MINUTES`. Falls back to the bare bracket when the zone cannot be named.
850
+ */
851
+ declare function formatVisitRange(iso: string): string;
852
+ /** "August 20, 9:20 PM EDT". */
853
+ declare function formatWhen(iso: string): string;
854
+ /**
855
+ * "Wed, August 20, 9:20 PM EDT". The booking confirmation only.
856
+ *
857
+ * The weekday is computed from the SAME instant in the SAME zone as the clock beside it, so the two cannot
858
+ * disagree.
859
+ */
860
+ declare function formatWhenWithDay(iso: string): string;
861
+
862
+ declare const TELEVET_BOOKING_COPY: Readonly<{
863
+ stateBlockedTitle: string;
864
+ noCliniciansTitle: string;
865
+ /** WHY, not just "none". An empty roster is a real, temporary answer. */
866
+ noCliniciansBody: string;
867
+ noCliniciansRetryCta: string;
868
+ noCliniciansRegionBody: string;
869
+ /** The matched vet, not a roster. */
870
+ yourVetTitle: string;
871
+ /** Suffix on the star line: "5.0 · 774 reviews". */
872
+ reviewsLabel: string;
873
+ rescheduleFromTemplate: string;
874
+ rescheduleFailed: string;
875
+ keepExistingCta: string;
876
+ bookingPausedTitle: string;
877
+ slotsErrorTitle: string;
878
+ noSlotsTitle: string;
879
+ /** The cross-clinician next opening, shown UNDER `noSlotsTitle`. `{when}` is DATA. */
880
+ nextOpeningTemplate: string;
881
+ /** The row that acts on it. Returns the owner to the clinician step. */
882
+ seeOtherVetsCta: string;
883
+ retryCta: string;
884
+ /** The hold lapsed while the owner was on a later step. */
885
+ holdExpired: string;
886
+ slotTaken: string;
887
+ reasonPlaceholder: string;
888
+ reasonTooLong: string;
889
+ /** Default OFF: sharing a pet's medical history is opt-in, and it is revocable afterwards. */
890
+ shareRecordsOff: string;
891
+ confirmNotEntitled: string;
892
+ confirmFailed: string;
893
+ checkAvailabilityCta: string;
894
+ /**
895
+ * THE STATE-INTEREST WRITE: the one way out of a refusal this flow cannot fix.
896
+ *
897
+ * PORTED FROM BOTH CONSUMER APPS, which agree on every string. Mobile's `liveVetCopy.region.waitlist*`
898
+ * (everfur-mobile src/features/liveVet/copy.ts:202-209, rendered by
899
+ * `TelevetStateInterestAction.tsx:27-51`) and the consumer WEB app's `ownerTelevetCopy.stateInterest*`
900
+ * (everfur-frontend app/product/televet/ownerCopy.ts:520-527, rendered by
901
+ * `StateInterestPanel.tsx:100-130`) are the same words. `submitting` exists only on the web side
902
+ * (ownerCopy.ts:524); mobile renders the pending state as a spinner inside its button instead, so the
903
+ * RN port uses `loading` and the DOM port uses this label, each following its own consumer app.
904
+ *
905
+ * NOT `waitlist` IN THE KEY NAMES. The server's enum calls this state interest and buckets it by REASON
906
+ * (`no_licensed_vet` vs `no_prescribing`), and the two are closed by different fixes. Naming the keys
907
+ * after the UI word would invite a call site to post the wrong bucket.
908
+ */
909
+ stateInterest: Readonly<{
910
+ cta: string;
911
+ /** Web only. Mobile shows a spinner in the button; see the block comment above. */
912
+ submitting: string;
913
+ joinedTitle: string;
914
+ joinedBody: string;
915
+ failed: string;
916
+ }>;
917
+ /**
918
+ * WHAT HAPPENED TO THE OWNER'S ATTACHMENTS, one sentence per cause.
919
+ *
920
+ * PORTED FROM BOTH CONSUMER APPS, which again agree character for character: mobile's
921
+ * `liveVetCopy.booking.media*Template` (everfur-mobile src/features/liveVet/copy.ts:484-491, rendered by
922
+ * `MediaDropNotice.tsx:31-36`) and the consumer WEB app's `ownerTelevetCopy.media.*Template`
923
+ * (everfur-frontend app/product/televet/ownerCopy.ts:486-494, rendered through
924
+ * `consultAttachmentShortfallLines` in consultAttachmentUpload.ts:382-407, whose own docblock calls itself
925
+ * "mobile's `MediaDropNotice` in pure form").
926
+ *
927
+ * `{n}` IS DATA and is filled at the call site through `fill`. Singular and plural are separate keys
928
+ * rather than a pluralisation rule, because that is what both consumer apps do and a rule invented here
929
+ * would produce a third grammar for the same sentence.
930
+ *
931
+ * WHY THIS DECK CARRIES `dropped` AT ALL, given the SDK never uploads. See `mediaShortfall.ts`: the SDK's
932
+ * booking sends `media_refs: []` unconditionally, so on this surface EVERY attachment an owner picked is
933
+ * dropped, and `dropped` is the sentence for exactly that. It is the most load-bearing line here, not a
934
+ * spare one.
935
+ */
936
+ media: Readonly<{
937
+ droppedTemplate: string;
938
+ droppedPluralTemplate: string;
939
+ refusedSizeTemplate: string;
940
+ refusedSizePluralTemplate: string;
941
+ refusedTypeTemplate: string;
942
+ refusedTypePluralTemplate: string;
943
+ refusedUnreadableTemplate: string;
944
+ refusedUnreadablePluralTemplate: string;
945
+ }>;
946
+ navTitle: string;
947
+ /** The chrome must not say "Book a visit" while the user is moving one they already have. */
948
+ rescheduleNavTitle: string;
949
+ close: string;
950
+ continueCta: string;
951
+ petSelectTitle: string;
952
+ addPetCta: string;
953
+ locationUnknownTitle: string;
954
+ locationUnknownBody: string;
955
+ locationUnknownCta: string;
956
+ locationBlockedTitle: string;
957
+ locationBlockedBody: string;
958
+ locationBlockedCta: string;
959
+ /** A real coordinate the host could not name. Retrying will not help. */
960
+ locationUnavailableTitle: string;
961
+ locationUnavailableBody: string;
962
+ stateAttestTemplate: string;
963
+ describeHeading: string;
964
+ describeHelpLong: string;
965
+ addPhotoCta: string;
966
+ removePhotoLabel: string;
967
+ summaryPet: string;
968
+ summaryVet: string;
969
+ summaryWhen: string;
970
+ summaryLength: string;
971
+ summaryReason: string;
972
+ /** "10 minutes" as a DERIVED fact: `{n}` is filled from `CONSULT_MINUTES` so the two cannot drift. */
973
+ summaryLengthTemplate: string;
974
+ confirmCta: string;
975
+ bookedTitle: string;
976
+ addToCalendarCta: string;
977
+ doneCta: string;
978
+ /** The confirmation's three steps. `{pet}` is DATA. */
979
+ booked: Readonly<{
980
+ remindPromise: string;
981
+ summaryPromise: string;
982
+ joinPromise: string;
983
+ backToPet: string;
984
+ calendarAdded: string;
985
+ calendarFailedBody: string;
986
+ }>;
987
+ /**
988
+ * THE FLOW CHROME, not a step: the shell every step renders inside, the discard guard, the hint on Close,
989
+ * and the states shown before any step screen has something to draw.
990
+ *
991
+ * `navTitle` and `close` are deliberately NOT duplicated here. The chrome reads the keys the flow already
992
+ * owns, so approving the bar's wording is one edit, not two that can drift apart.
993
+ */
994
+ chrome: Readonly<{
995
+ /** Announced after "Close", so a screen reader hears what the press costs. */
996
+ closeHint: string;
997
+ discardTitle: string;
998
+ discardBody: string;
999
+ discardConfirmCta: string;
1000
+ discardKeepCta: string;
1001
+ noPetsTitle: string;
1002
+ loadFailedTitle: string;
1003
+ retryCta: string;
1004
+ }>;
1005
+ /**
1006
+ * The emergency refusal. This is the one screen on the surface that interrupts.
1007
+ *
1008
+ * THE EXIT ROWS. This used to say the three rows were missing "in the consumer and here", which was wrong
1009
+ * twice: the consumer mobile screen draws the find-a-vet row and the primary-vet row
1010
+ * (TelevetEarlyEmergency.tsx:73-75, :91) and the consumer web draws all three with poison control
1011
+ * (BookingFunnel.tsx:713-774). The RN screens now draw the mobile two, the find row only when the host
1012
+ * passes its own locator (`onFindEmergencyVet`, the SDK opens nothing outside the app) and the clinic row
1013
+ * only when the host holds the clinic (`primaryVet`). The web screens draw the web cards from
1014
+ * `bookingCopyWeb.ts` on the same two host seams, with poison control held for the owner's approval (see
1015
+ * `src/web/televet/booking/EmergencyExits.tsx`). No number is ever invented for this screen.
1016
+ */
1017
+ emergency: Readonly<{
1018
+ bannerTitle: string;
1019
+ keepBookingCta: string;
1020
+ lede: string;
1021
+ exitPrimaryVetTitle: string;
1022
+ /** everfur-mobile copy.ts:734 `emergency.exitFindErTitle`, the consumer web's `findEmergencyVet` word for word. */
1023
+ exitFindErTitle: string;
1024
+ startCallHint: string;
1025
+ }>;
1026
+ /** The calendar entry's own title, from the consumer's `visit` block. */
1027
+ visit: Readonly<{
1028
+ title: string;
1029
+ }>;
1030
+ /**
1031
+ * THE SPOKEN NAME OF EACH LOADING REGION, AND THE SPOKEN POSITION OF THE STEP RAIL.
1032
+ *
1033
+ * ── WHY THESE EXIST AT ALL ───────────────────────────────────────────────────────────────────────────
1034
+ * Each booking step renders its wait as a `role="progressbar"` container of grey blocks. The blocks are
1035
+ * `aria-hidden` (a reader announcing eight rectangles is noise), so the CONTAINER's accessible name is the
1036
+ * only thing a screen-reader user is told while the step is loading. Six of these containers shipped with
1037
+ * no name at all, which a reader announces as an unnamed busy region: the user hears that something is
1038
+ * happening and nothing about what. The records port established the convention this fixes
1039
+ * (`DASHBOARD_COPY.loading`, `REQUEST_SCREEN_COPY.loading`, `DEPTH_COPY.loadingTimeline`,
1040
+ * `CLINIC_PICKER_COPY.loading`, `SHARE_COPY.loadingLinks`): the name is an `aria-label` sourced from a
1041
+ * copy module, phrased as what is being loaded.
1042
+ *
1043
+ * ── THESE WERE BRACKETED PLACEHOLDERS, AND THEY ARE NOT ANY MORE. WHAT CHANGED IS THE SOURCE ─────────
1044
+ * The previous round wrote `[PLACEHOLDER: ...]` here on the reasoning that the consumer booking deck has
1045
+ * no loading strings, so there was nothing to port and inventing the words would be this SDK authoring
1046
+ * product voice for a partner's customers. The first half of that was simply not measured: the CONSUMER
1047
+ * WEB APP has carried both of these strings for months, and the round that bracketed these keys looked at
1048
+ * the React Native deck rather than at the web app this surface is a port OF.
1049
+ *
1050
+ * `everfur-frontend app/product/televet/components/SystemStates.tsx:46`
1051
+ * `const LOADING_LABEL_COPY = 'Loading. Nothing to read yet.';`
1052
+ * and its own docblock, verbatim: "The written line says that this region is fetching and has nothing
1053
+ * to read yet, and it NAMES NO SCREEN, because the default is rendered on five different ones. A caller
1054
+ * that knows its surface should pass `label` instead, which is a one-line change at each call site and
1055
+ * the reason the prop exists."
1056
+ *
1057
+ * `everfur-frontend app/product/see-a-vet/copy.ts:194`
1058
+ * `progressLabel: (step: number, total: number) => `Step ${step} of ${total}``
1059
+ *
1060
+ * So the convention the consumer settled is: ONE written sentence for the generic busy region, and a
1061
+ * per-surface name where the caller knows its surface. That is what this block now is. Each name is the
1062
+ * consumer's own sentence with the surface named, in the consumer's own grammar.
1063
+ *
1064
+ * ── WHY AUTHORING THESE IS IN SCOPE AND AUTHORING THE REST IS NOT ────────────────────────────────────
1065
+ * The consumer copy deck draws this exact fence around this exact category, and the wording is worth
1066
+ * quoting because it is the licence under which these five keys are written
1067
+ * (`everfur-frontend app/product/see-a-vet/copy.ts:185-189`):
1068
+ *
1069
+ * "assistive-technology labels: the narrowest category of string it is defensible for an engineer to
1070
+ * author, and none of them makes a clinical, urgency or prescribing claim. They still want review
1071
+ * before this page is armed."
1072
+ *
1073
+ * Every value below is a functional name for a region or a position. None names a product, makes a
1074
+ * clinical or urgency claim, promises a time, or says anything an owner could act on wrongly. They are
1075
+ * what a screen reader says INSTEAD OF SILENCE. The rest of this deck stays draft-ported and the ship
1076
+ * gate below still holds it, because a marketing or clinical sentence is a different category and this
1077
+ * fence is the reason the two can be told apart.
1078
+ */
1079
+ loading: Readonly<{
1080
+ /**
1081
+ * The pet step's roster read.
1082
+ *
1083
+ * "Pets" and not "Your pets": the host supplies the account, and on a partner's multi-pet dashboard the
1084
+ * surface may be mounted for a household rather than for one person. The consumer's own default names
1085
+ * no owner for the same reason it names no screen.
1086
+ */
1087
+ pets: "Loading pets. Nothing to read yet.";
1088
+ /** The clinician step's roster read. "Vets", the word every consumer frame uses for this list. */
1089
+ clinicians: "Loading vets. Nothing to read yet.";
1090
+ /**
1091
+ * The slot step's availability read, and the reschedule screen's own slot read.
1092
+ *
1093
+ * "Times" rather than "availability" or "slots": `3668:5240` is headed "Pick a date and time", so this
1094
+ * is the word the owner has just read on the screen the wait belongs to.
1095
+ */
1096
+ slots: "Loading times. Nothing to read yet.";
1097
+ /** The reschedule screen waiting for the consult it is moving. "Visit", as `visit.title` already says. */
1098
+ consult: "Loading your visit. Nothing to read yet.";
1099
+ /**
1100
+ * THE STEP RAIL'S SPOKEN POSITION, and it is a TEMPLATE rather than a name, because the consumer does
1101
+ * not name this region at all.
1102
+ *
1103
+ * `FunnelProgress` (`everfur-frontend app/product/televet/components/BookingFunnel.tsx:126-141`) draws
1104
+ * the rail as `aria-hidden` bars beside ONE `sr-only` paragraph reading
1105
+ * `intakeSystemCopy.progressLabel(current, total)`, and its docblock states the rule: "It is a labelled
1106
+ * group, not decoration: the position is stated in text for assistive technology, in the intake's own
1107
+ * words, and the bars are `aria-hidden` so nothing reads eight empty items after it."
1108
+ *
1109
+ * A `progressbar` with a value and a region NAME is a different announcement from the consumer's: the
1110
+ * reader says the name, then "4 of 6", which is two utterances for one fact and neither of them is the
1111
+ * sentence the consumer app says. So the web chrome now carries the consumer's sentence, filled with the
1112
+ * step and the total, and `{step}` / `{total}` are DATA through `fill`.
1113
+ */
1114
+ progressTemplate: "Step {step} of {total}";
1115
+ }>;
1116
+ }>;
1117
+ type TelevetBookingCopy = typeof TELEVET_BOOKING_COPY;
1118
+ /**
1119
+ * THE SHIP GATE. False while the deck is unapproved OR any value is still a bracketed placeholder, so a
1120
+ * partial approval cannot half-ship. Deliberately blunt: the failure it prevents is a partner's user reading
1121
+ * a string Everfur drafted for its own app and never signed off for someone else's.
1122
+ *
1123
+ * BOTH CONDITIONS ARE STILL CHECKED EVEN THOUGH ONE OF THEM NOW HAS NOTHING TO FIND. The placeholder scan
1124
+ * finds no placeholder in the deck today, which is the point of the accessibility names having landed; it
1125
+ * stays because the next string added here is as likely to be a bracket as not, and a gate deleted once it
1126
+ * passes is a gate that was never load-bearing. `TELEVET_BOOKING_COPY_APPROVED` is the condition holding the
1127
+ * screens dark now, and its own note says why that is the honest answer rather than a leftover.
1128
+ *
1129
+ * THIS IS LOAD-BEARING, not advisory. `EverfurTelevetBooking`, `EverfurTelevetReschedule` and
1130
+ * `TelevetBookedScreen` each refuse to render their ported screens while it answers false, and hand over to
1131
+ * the hosted visit instead. See `tests/component/televetCopyGate.test.tsx`, which mounts them and pins it.
1132
+ */
1133
+ declare function isTelevetBookingCopyReady(): boolean;
1134
+
1135
+ export { createTelevetBookingController as $, type TelevetBookingCopy as A, type TelevetBookingDraft as B, CANCELLATION_POLICY_VERSION_MAX as C, type TelevetBookingEntitlement as D, type TelevetBookingFailure as E, type TelevetBookingRefusal as F, type TelevetBookingState as G, type TelevetCancellationPolicy as H, type TelevetClinician as I, type TelevetEarlyTriage as J, type TelevetEmptyRosterView as K, type TelevetJoinReadiness as L, MEDIA_REFS_MAX as M, NO_MEDIA_SHORTFALL as N, type TelevetJoinState as O, type TelevetMediaRefusal as P, type TelevetRead as Q, REASON_TEXT_MAX as R, type TelevetSlotHold as S, type TelevetEmptyRosterCause as T, type TelevetStateInterestReason as U, type TelevetStatePolicy as V, type TelevetTriageVerdict as W, type TelevetWrite as X, attestation as Y, canConfirm as Z, canManageTelevetVisit as _, type TelevetStateInterestAction as a, currentStep as a0, emptyTelevetDraft as a1, formatVisitRange as a2, formatWhen as a3, formatWhenWithDay as a4, hasPreviousStep as a5, initialTelevetBookingState as a6, isHoldExpired as a7, isMediaShortfallEmpty as a8, isSkipped as a9, useTelevetJoin as aA, useTelevetNextSlot as aB, useTelevetRescheduleConsult as aC, useTelevetSlots as aD, useTelevetStatePolicy as aE, validateBookingBody as aF, withUnsentPicks as aG, type TelevetJoinBlock as aH, type TelevetJoinDecision as aI, televetJoinDecision as aJ, isTelevetBookingCopyReady as aa, isWellFormedStateCode as ab, mediaShortfallFromUnsentPicks as ac, splitTelevetVisits as ad, televetBookingKey as ae, televetBookingReducer as af, televetBookingRefusal as ag, televetCancelCall as ah, televetConsultCall as ai, televetEmptyRosterCause as aj, televetEmptyRosterView as ak, televetHoldCall as al, televetJoinCall as am, televetJoinOpensAtMs as an, televetJoinReadiness as ao, televetMediaShortfallLines as ap, televetRescheduleCall as aq, useEarlyTriage as ar, useStateInterest as as, useTelevetBookConsult as at, useTelevetBookingAvailable as au, useTelevetBookingController as av, useTelevetBookingEntitlement as aw, useTelevetCancellationPolicy as ax, useTelevetClinicians as ay, useTelevetHoldSlot as az, type TelevetMediaShortfall as b, type TelevetConsult as c, type TelevetBookingStep as d, type TelevetBookingController as e, type TelevetTriageRead as f, type TelevetStateInterestWrite as g, type TelevetCancelReceipt as h, type TelevetSlot as i, CANCEL_REASON_MAX as j, CONSULT_KINDS as k, CONSULT_MINUTES as l, CONSULT_STATUSES as m, type ConsultKind as n, type ConsultStatus as o, TELEVET_BOOKING_BASE as p, TELEVET_BOOKING_COPY as q, TELEVET_BOOKING_STEPS as r, TELEVET_CONSULT_PAGE_LIMIT as s, TELEVET_JOIN_CLOSES_MINUTES_AFTER as t, TELEVET_JOIN_OPENS_MINUTES_BEFORE as u, TERMINAL_CONSULT_STATUSES as v, type TelevetBookingAction as w, type TelevetBookingBody as x, type TelevetBookingContext as y, type TelevetBookingControllerDeps as z };