@apex-inc/sdk 0.20.1 → 0.21.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.
@@ -73,7 +73,13 @@ Interactive flows: MCP prompts `new-experiment` and `experiment-review` orchestr
73
73
  - **Target URL is this workspace.** `target_url` must be a host named on the **active** workspace (`list_workspace_environments` + the workspace site). If the merchant already named a workspace, switch to it — do not ask which workspace again. If they have multiple hosts on that workspace, ask which URL. Never guess `apex.inc` or any other workspace's domain.
74
74
  - **Mobile/Capacitor experiments capture on-device.** When the experiment runs on an authed in-app screen (servers can't reach it), tell the dev to call `Apex.captureVariantScreenshot({ experimentId, variantKey })` on the variant's screen, keyed to the resolved variant in a `useEffect`, in a debug build (`Apex.initialize({ ..., debug: true })`). It no-ops in production and lands the shot on the dashboard card + gallery like web/agent captures.
75
75
  - **Exposure auto-fires.** When a variant resolves via `useApexVariant` (web) or `Apex.getVariant()` (mobile), the SDK fires the canonical `experiment_exposure` event — the denominator for results. You don't fire it manually.
76
- - **Preview on Next.js.** `useApexVariant` cannot read `window` during SSR. Pass the request search string (`useApexVariant(id, { search })` from `searchParams`, or `<ApexProvider search>`) so the first HTML matches `_apex_preview`. Without it the server renders control and the arm only appears after hydrate.
76
+ - **First HTML / flicker.** Merchants still write `const variant = useApexVariant("exp_…")` and branch in React. That hook is not flicker-free by itself. First HTML is flicker-free only when it is already the assigned arm.
77
+ - **SSR (Next, Remix):** on the document request, mint or read the `apex_vid` cookie. Assign with the same rules as `GET /api/experiments/{id}/assign`. Pass that arm: `useApexVariant(id, { initialVariant, search })` or `<ApexProvider assignments={{ [id]: variant }}>`. `{ search }` also makes `_apex_preview` match on first HTML. Without the server arm, the first HTML is control; after `/assign` returns, React swaps. That swap is the flicker.
78
+ - **If you use Next.js:** `npm install @apex-inc/next` (visitor middleware + `assignExperiments`). Pass the result to `<ApexProvider assignments>`.
79
+ - **Client-only SPA:** the hook cannot know the arm before first HTML. Do not promise no flicker.
80
+ - **Snippet mode** (Apex mutates the DOM) is a different path. Do not conflate. Do not hide the page until `/assign` returns as the React-hook promise (worse LCP).
81
+ - Creating or activating an experiment (MCP or API) does not serve the visitor page and does not remove flicker. The merchant app must still pass the server arm.
82
+ - Pass the server arm into the hook or provider. `@apex-inc/react@0.4.3` accepts `initialVariant` and `ApexProvider assignments`.
77
83
  - **Don't launch a dead experiment.** `activate_experiment` is gated on `verify_experiment_wiring`. Code-wired (SDK-hook) web experiments are safe to activate before their code ships: the server holds them in `pending_deployment` ("Waiting on deploy"). The 14-day clock starts at the first **recorded** visit (a tester on a named Beta site, or a live visit) — not merge, not activate. Testers on a named Beta site count in the winner. Localhost/dev is recorded as Dev and does not start the window or decide the winner. Assign with no Origin is Unclassified (no enrollment). Backend assign must send `environment` or `x-apex-web-environment`. Non-SDK experiments with unwired arms are refused (pass `force: true` to override). Run `verify_experiment_wiring({ experimentId })` after the deploy lands to confirm both arms are live.
78
84
 
79
85
  ## The winner is the control
@@ -116,6 +122,8 @@ Omitting `intent` when it's required throws a 409 `intent_required` naming both
116
122
 
117
123
  `GET /api/experiments/{experimentId}/assign` returns `{ variant, experimentId, visitorId }`. Assignment is **deterministic** from a stable visitor id (cookie `apex_vid` in browsers).
118
124
 
125
+ The same assign call is how a Next/Remix document request gets flicker-free first HTML: read or mint `apex_vid`, assign, pass the arm into `useApexVariant` / `ApexProvider`. If you use Next.js, `@apex-inc/next` does that. See First HTML / flicker above.
126
+
119
127
  For **server-side** sends (e.g. PLG email or API-triggered notifications):
120
128
 
121
129
  1. Use a **stable string per recipient** as the visitor id (e.g. hashed user id). Reuse the same id for every assignment call for that user for that experiment.
@@ -127,7 +135,7 @@ Then branch notification copy or UI on `variant` (`control` vs `variant_b`).
127
135
  ## Instrumentation checklist
128
136
 
129
137
  1. **Create and activate** the experiment in Apex (UI or MCP).
130
- 2. **Assign variant** before rendering the experience.
138
+ 2. **Assign variant** before rendering the experience. On SSR, that means on the document request, then pass the arm into the hook or provider.
131
139
  3. **Track primary metric** events with consistent names (e.g. `notification_sent`, `notification_opened`, `goal_completed`) and include `experimentId` and `variant` in properties when possible.
132
140
  4. **Log a prediction** before launch when using the full intelligence loop (calibration).
133
141
  5. **Decide** with `get_results` / dashboard — avoid peeking without a rule (pre-registered sample size, Bayesian policy, or fixed run duration).
@@ -142,6 +150,8 @@ Match Apex UI terminology in user-facing copy: e.g. **Adaptive traffic allocatio
142
150
  - Declaring an SDK / Cursor draft ready from hosted screenshots of a public URL. The variant is local until deploy — attach localhost shots with `attach_experiment_asset`.
143
151
  - Starting an experiment without a **clear primary metric** tied to the belief.
144
152
  - Using different visitor ids for the same user across channels (breaks consistent assignment).
153
+ - Promising no flicker from `useApexVariant` without passing a server arm. Creating the experiment does not remove flicker.
154
+ - Hiding the page until `/assign` returns (worse LCP; not the React-hook promise).
145
155
  - Changing variant implementation mid-flight without versioning (invalidates analysis).
146
156
  - Only using p-values without effect size or business context.
147
157
 
@@ -27,7 +27,7 @@ Events are batched and sent to `POST /api/events` automatically. Call `flush()`
27
27
 
28
28
  ```typescript
29
29
  // On signup
30
- track("signup_completed", { method: "email", plan: "free" });
30
+ track("user_signed_up", { method: "email", plan: "free" });
31
31
  identify(user.id, {
32
32
  email: user.email,
33
33
  name: user.name,
@@ -210,7 +210,7 @@ still reduces revenue, but leaves the margin untouched.
210
210
 
211
211
  ## Event naming conventions
212
212
 
213
- - **snake_case**: `signup_completed`, `feature_used`, `invoice_paid`
213
+ - **snake_case**: `user_signed_up`, `feature_used`, `invoice_paid`
214
214
  - Context in **properties**: `track("ui_action", { action: "click", name: "Start trial", location: "pricing" })`
215
215
  - For experiments, include `experimentId` and `variant` in properties
216
216
  - One primary metric per experiment; secondary metrics as properties
@@ -313,11 +313,11 @@ import { createManagementClient } from "@apex-inc/sdk";
313
313
  const apex = createManagementClient({ apiKey, workspaceKey, apiUrl });
314
314
 
315
315
  // Define the primary conversion — a custom_event matching the Apex Spec
316
- // event you send via apex.track("signup_completed", …)
316
+ // event you send via apex.track("user_signed_up", …)
317
317
  const signup = await apex.conversions.create({
318
318
  name: "Signup",
319
319
  type: "custom_event",
320
- eventName: "signup_completed",
320
+ eventName: "user_signed_up",
321
321
  isPrimary: true,
322
322
  });
323
323
 
@@ -23,7 +23,11 @@ Call `check_workspace_readiness` (pass the current `owner/repo`). Resolve gaps:
23
23
  max duration, stopping rule) — all four are required to arm the gate.
24
24
  2. `create_experiment` → `exp_id`.
25
25
  3. Author the variant in the IDE: wrap the target with `useApexVariant("exp_id")`, add the
26
- variant branch. Show the diff; let the developer tweak.
26
+ variant branch. On SSR, assign on the document request (mint or read `apex_vid`, same
27
+ rules as `GET /api/experiments/{id}/assign`) and pass that arm (`initialVariant` or
28
+ `<ApexProvider assignments>`). If you use Next.js, `npm install @apex-inc/next` and
29
+ pass `assignExperiments` into `<ApexProvider assignments>`. The hook alone is not flicker-free. A client-only SPA
30
+ cannot know the arm before first HTML. Show the diff; let the developer tweak.
27
31
  4. Commit with a Conventional Commit message that includes the id, e.g.
28
32
  `feat(apex): move pricing CTA above the fold for exp_482`. Add a
29
33
  `Co-Authored-By: Apex <apex@apex.inc>` trailer **unless** the workspace's "Apex