@revturbine/sdk 0.11.16 → 0.11.18

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.
package/CHANGELOG.md CHANGED
@@ -47,6 +47,224 @@ also require a changelog entry.
47
47
 
48
48
  ---
49
49
 
50
+ ## 0.11.18
51
+
52
+ ### Scaffold pin 0.1.383: interaction decision context moves onto core's input type; `growth_signal_observed` retired (plan 282 TASK-9 follow-up, BL-0463)
53
+
54
+ **What changed.** Three related changes; none of them changes
55
+ `trackTreatmentInteraction`'s accepted shape.
56
+
57
+ - **`@revt-eng/core` / `@revt-eng/schema` / `@revt-eng/schema-external`**
58
+ pinned 0.1.381 → 0.1.383 (scaffold #438, #439, #440). Re-vendored
59
+ `server-python/src/revturbine/types.py` and `server-rust/src/types.rs`;
60
+ regenerated `web-sdk/generated/openapi.d.ts` (byte-identical — no
61
+ external-tagged operation changed) and `web-sdk/generated/public-api.json`.
62
+ - **`RevTurbineTreatmentInteractionRequest`** is now a plain alias of core's
63
+ `RevTurbineTreatmentInteractionInput`. 0.11.17 defined this SDK's own
64
+ `RevTurbineTreatmentInteractionDecisionContext` (`decisionId`,
65
+ `segmentHandles`, `segmentIds`) and intersected it onto the input type
66
+ because core did not carry those fields yet. Scaffold 0.1.383 moved that
67
+ same shape — TSDoc included — onto `RevTurbineTreatmentInteractionInput`
68
+ itself (#439), so the local interface and the intersection are gone. The
69
+ shape `trackTreatmentInteraction` accepts is byte-identical to 0.11.17; this
70
+ is a refactor of where the type is declared, not a signature change.
71
+ - **`growth_signal_observed`** is retired from the platform event taxonomy
72
+ (v6 → v7, scaffold #440): it and `GrowthSignalObservedPayload` no longer
73
+ exist. `EmittablePlatformEventName` no longer accepts the literal, so code
74
+ naming it in `emitPlatformEvent('growth_signal_observed', …)` now fails to
75
+ compile. No SDK code emitted this event internally.
76
+
77
+ **Landed in** 0.11.18. **Fail-closed in** 0.11.18 — the taxonomy removal is a
78
+ type-level break with no silent-tolerate window: a caller passing the retired
79
+ literal gets a compile error immediately, not a runtime no-op.
80
+
81
+ **Proving tests.** `web-sdk/interaction-decision-context.test.ts` (unchanged,
82
+ still green against the aliased type), `web-sdk/interaction-wire-contract.test.ts`,
83
+ `web-sdk/platform-emit-exclusion.test-d.ts` (`check:types:exact`),
84
+ `check:public-api`.
85
+
86
+ ## 0.11.17
87
+
88
+ ### Checkout metadata helper, `checkout_started`, and decision context on interactions (plan 282 TASK-9, BL-0432)
89
+
90
+ **What changed.** Three additive changes to the browser SDK (`@revturbine/sdk`);
91
+ no existing signature narrows.
92
+
93
+ - **`checkoutMetadata(outputId)`** (new, `@public`) returns the Stripe Checkout
94
+ `revturbine_*` metadata bag for an output this SDK decided — user, account,
95
+ placement, decision, output, rule and target plan — plus
96
+ `client_reference_id` (the user id). Pass `metadata` as both the session's
97
+ `metadata` and `subscription_data.metadata`; the mirror is what reaches each
98
+ invoice's `parent.subscription_details.metadata`. A key the SDK does not hold
99
+ is absent, never empty; `null` for an output this SDK did not decide. New
100
+ types `RevTurbineCheckoutMetadata` and `RevTurbineCheckoutMetadataBag`.
101
+ - **`checkout_started`** (taxonomy v6, `@revt-eng/schema` 0.1.381) is emitted on
102
+ a `cta_clicked` whose CTA opens a plan picker or pricing page
103
+ (`CHECKOUT_STARTED_CTA_ACTION_TYPES`: `navigate_to_plans`, `view_plans`) with
104
+ `{ placement_id, payload_id, decision_id, plan_handle, rule_handle }`. Never
105
+ for `open_checkout_modal` — that CTA opens checkout directly ("on click"), so
106
+ its started count is its clicked count.
107
+ - **`trackTreatmentInteraction`** accepts optional `decisionId`,
108
+ `segmentHandles` and `segmentIds` (`RevTurbineTreatmentInteractionDecisionContext`;
109
+ the parameter type is now `RevTurbineTreatmentInteractionRequest`, a
110
+ superset of the previous `RevTurbineTreatmentInteractionInput`) and sends
111
+ them as `decision_id` / `segment_handles` / `segment_ids`. Segments are
112
+ handles: `segment_handles` is the effective set scaffold's `evaluateSegments`
113
+ built plus any hosted-resolved slugs — the analytics join key — and
114
+ `segment_ids` carries only the minted ids a hosted context supplied
115
+ (telemetry; omitted entirely in local mode). When not given, they are filled
116
+ from the output this SDK indexed for `payloadId`; a caller that gives none
117
+ and names no indexed output sends exactly the row it sent before. The React
118
+ controller's clicks and the output-addressed `convert()` / `dismiss()` /
119
+ `snooze()` carry them.
120
+
121
+ **Landed in** 0.11.17. **Fail-closed in** n/a — additive. The headless ports
122
+ (Python, Rust, `server-node`) carry only the re-vendored generated types for
123
+ the 0.1.381 pin; the cross-language parity corpus is untouched.
124
+
125
+ **Docs.** `guides/billing.mdx` "Where checkout fits" now documents the bag, the
126
+ `subscription_data.metadata` mirror, `client_reference_id`, and that Customer
127
+ metadata cannot be set from Checkout.
128
+
129
+ **Proving tests.** `web-sdk/checkout-metadata.test.ts`,
130
+ `web-sdk/checkout-started-emit.test.ts`,
131
+ `web-sdk/interaction-decision-context.test.ts`.
132
+
133
+ ### `update({ usage })` now reaches usage and credit threshold placements and their usage tokens (BL-0402, BL-0409)
134
+
135
+ **What changed.** A behaviour fix in the browser SDK (`@revturbine/sdk`); no
136
+ signature changes. `update({ usage })` / `updateUsage()` wrote only the SDK's
137
+ private usage balances. `can()` and `getUsage()` read those balances, but the
138
+ placement resolver built its usage state from the user context's `usage`
139
+ entries alone, and took each entry's `limit` only from a per-entry field
140
+ (default `0`). The shared threshold gate fails closed on a missing or zero
141
+ limit. So `usage_threshold` and `credit_threshold` placements (the 80% usage
142
+ banner, the low-credits warning) never fired for app-reported usage, and
143
+ `{{usage_current}}` / `{{usage_limit}}` / `{{usage_remaining}}` /
144
+ `{{usage_percent}}` stayed stale or unresolved, while the gate and the meter
145
+ moved.
146
+
147
+ Placements now read one usage view, the same one `getUsage()` reports:
148
+
149
+ - **Used**: the balance the app reported through `update({ usage })` /
150
+ `updateUsage()` / `identify(…, { usage })` wins (the most recent write),
151
+ else the context entry's `amount`.
152
+ - **Limit**: the context entry's own `limit` wins, else the Playbook
153
+ allowance for the user's **current plan** (the plan-scoped `usage_limit`
154
+ rule's `limit_value` or `credits` rule's `allowance_value`, which is the
155
+ `limit` `getUsage()` already returned). An `unlimited` or absent allowance
156
+ means no threshold: the placement does not fire.
157
+ - **Tokens**: the SDK now fills the usage token family in decision content
158
+ from the same view. It fills `{{usage_current}}` whenever usage is known.
159
+ It fills `{{usage_limit}}`, `{{usage_remaining}}` and `{{usage_percent}}`
160
+ only when a positive limit is known, so an unlimited allowance leaves
161
+ those tokens untouched instead of rendering `0`. The `<unit>_usage_*`
162
+ forms and the aliases (`current_usage`, `current_limit`,
163
+ `remaining_usage`) are filled too. A registered entitlements provider
164
+ still supplies its own usage state, as before.
165
+
166
+ `can()` and `getUsage()` are unchanged. The headless server ports (Python,
167
+ Rust, `server-node`) take usage counters from the caller and are unaffected;
168
+ the cross-language parity corpus is untouched.
169
+
170
+ **Docs.** `tutorials/usage-quota-meter.mdx` and
171
+ `tutorials/low-credits-warning.mdx` now use the canonical flat Playbook rule
172
+ shape (`kind` + `limit_value` / `allowance_value`, handle-valued ids). The
173
+ old nested `type_fields` shape resolved no allowance, so neither tutorial's
174
+ banner could fire. The expected-behaviour table now matches what the SDK
175
+ does: the banner shows from 80%, and at 100% `can('api_calls')` denies.
176
+
177
+ **Proving test.** `web-sdk/usage-reaches-placements.test.ts`.
178
+
179
+ ### App-supplied trial context now reaches trial placements, trial tokens and reverse-trial grants (BL-0403)
180
+
181
+ **What changed.** A behaviour fix in the browser SDK (`@revturbine/sdk`); no
182
+ signature changes. `user.trial` at init, `update({ trial })`,
183
+ `setUserContext({ trial })` and a server action's `userContext` result all
184
+ merged into the user context's `trial`, but neither trial reader looked there.
185
+ The placement resolver's plan state (which gates `trial_started`,
186
+ `trial_progress`, `trial_ending`, `trial_ended` and `trial_converted`
187
+ placements) and the reverse-trial entitlement grants read only the SDK's own
188
+ trial status, written by `localRuntime.initialData.trialStatus`, `hydrate()`,
189
+ `getTrialStatus()` and `setTrialInstances()`. So an app that supplied its
190
+ trial saw no trial placement fire, no reverse-trial grant apply, and
191
+ `{{trial_days_remaining}}` stay raw.
192
+
193
+ Both readers now use one trial view:
194
+
195
+ - **Sources.** The user context's `trial` (what the app supplies, plus the
196
+ authenticated client-context delivery, which merges its trial into the same
197
+ field) and the SDK trial status (`initialData.trialStatus`, `hydrate()`,
198
+ `getTrialStatus()`, `setTrialInstances()`).
199
+ - **Precedence: the most recent change wins**, the same rule the usage view
200
+ applies to reported balances (BL-0402). A server-delivered or hydrated
201
+ status wins when it arrives after the app's trial (an
202
+ `initialData.trialStatus` passed alongside init `user.trial` counts as
203
+ later). An app `update({ trial })` made after it wins over it. Re-sending
204
+ an unchanged trial is not a newer write, so passing the same trial on every
205
+ render never displaces a fresher server status. A source that was never
206
+ supplied does not compete.
207
+ - **Tokens.** The SDK now fills `{{trial_days_remaining}}` and
208
+ `{{trial_days_total}}` in decision content from the same plan state the
209
+ trial gate read. A token whose value is unknown is left untouched.
210
+ - **`getTrialStatus()`** in `local_only` mode with no `getTrialStatus`
211
+ resolver returns this trial view, the app-supplied trial included.
212
+
213
+ **React.** `<RevTurbineProvider>` no longer reports `trial`, `tiers`,
214
+ `payment_failed`, `payment_at_risk`, `instances`, `experiments`, `email_type`,
215
+ `derived_config_version`, `context_hash` or `derived_computed_at` in
216
+ `options.user` as "dropped unrecognized user-context key(s)" on mount. The SDK
217
+ constructor already merges them from `options.user`; the provider now forwards
218
+ only the identity fields `identify()` merges. Unknown keys and server-assigned
219
+ keys are still reported.
220
+
221
+ The headless server ports (Python, Rust, `server-node`) take an
222
+ already-derived trial status and are unaffected; the cross-language parity
223
+ corpus is untouched.
224
+
225
+ **Proving tests.** `web-sdk/trial-reaches-placements.test.ts`,
226
+ `web-sdk/react/RevTurbineProvider.identify-context.test.tsx`.
227
+
228
+ ### Trial state no longer leaks across users; app-supplied trials fire lifecycle triggers; `getUserContext()` reports trial and billing fields (BL-0417)
229
+
230
+ **What changed.** Behaviour fixes in the browser SDK (`@revturbine/sdk`)
231
+ that follow up BL-0403. No signature changes.
232
+
233
+ - **Reset and user switch.** `resetIdentity()` / `resetUserContext()`
234
+ cleared the user context but kept the SDK trial status (from
235
+ `initialData.trialStatus`, `hydrate()`, `getTrialStatus()` or
236
+ `setTrialInstances()`). They also kept which trial source was newest and
237
+ the last lifecycle stage. So the next user saw the previous user's trial
238
+ placements, reverse-trial grants and `{{trial_days_*}}` tokens, and a
239
+ trial-less next user could emit `trial_expired`. A reset now clears all of
240
+ that, and the next user starts with no trial. Identifying a **different**
241
+ user without a reset (`identify()`, `setUserContext({ id })`, or a
242
+ `hydrate()` payload for another user) now drops the previous user's trial
243
+ (both sources) and usage (reported balances and context `usage`) before
244
+ merging the new user's values. The new user's own values still apply. Going
245
+ from anonymous to identified, or re-identifying the same user, keeps what
246
+ is held. Reported usage was already cleared on reset; only the
247
+ switch-without-reset path leaked it.
248
+ - **Lifecycle triggers.** `trial_midpoint`, `trial_expiring` and
249
+ `trial_expired` fired only from `getTrialStatus()` and
250
+ `setTrialInstances()`. The SDK now also runs the lifecycle evaluation
251
+ against the resolved trial view after every change that can move it:
252
+ `user.trial` at init, `update({ trial })`, `setUserContext`, a server
253
+ action's context, the client-context delivery, `hydrate()`, a reset, and a
254
+ Playbook load that turns the triggers on. The dedupe has not changed: a
255
+ stage fires only when it differs from the last stage seen. So an unchanged
256
+ trial, or both sources reporting the same threshold, fires once.
257
+ `enableTrialAutoTriggers: false` still silences them.
258
+ - **`getUserContext()`** now includes `trial` (the resolved trial view, the
259
+ one placements and grants decide with), `tiers`, `payment_failed`,
260
+ `payment_at_risk` and `instances` when they are held. The snapshot is a
261
+ copy, so changing it does not change SDK state.
262
+
263
+ The headless server ports are unaffected, and the cross-language parity
264
+ corpus is untouched.
265
+
266
+ **Proving test.** `web-sdk/trial-reset-lifecycle.test.ts`.
267
+
50
268
  ## 0.11.16
51
269
 
52
270
  ### Build fix, part two: the npm declaration build resolves `openapi-fetch` too (no API change)