qs-02: Experiment preview (debug token + preview links) - #415
Conversation
Pure exported helper parsePreviewParam(value) -> {experienceId,variationId}|null,
dot-separated numeric ids, exported from js-sdk public entry. 12-row case-table test.
Beads: ai-driven-product-dev-hooy
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ygiene (AC1,AC2) Add debugToken?:string to ConfigBase; ApiManager appends debug_token=<value> and forces _conv_low_cache=1 to the config-fetch URL when set (proper &-joined query). Token never sent to track endpoint nor logged. Config-matrix + hygiene tests (6) + api suite (16) green. Beads: ai-driven-product-dev-m568 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…emo (AC3-fetch,AC8) ApiManager.getConfigByExperience(id) fetches /config/<sdkKey>?exp=<id>&_conv_low_cache=1 (+debug_token when configured), memoized process-wide in a module-level Map keyed sdkKey:experienceId, 60s TTL (live Date.now), in-flight promise cached to collapse concurrent calls to one fetch. 7 tests + 23 api suite green. Beads: ai-driven-product-dev-e0aw Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ger (AC4,AC7) Pure DataManager.getPreviewDecision(experience, variationId) resolves the variation directly from the passed experience.variations (not shared config), returns a BucketedVariation-shaped decision bypassing rules/env/status/traffic/stored/hash, with no putData and no enqueue. Unknown variation -> null. 7 tests + 104 data suite green. Beads: ai-driven-product-dev-rr3l Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…isolation (AC3,AC5,AC6,AC7)
Add enableStorage flag (gates putData in DataManager fresh-bucketing branch). Context.setPreview
resolves via shared config or getConfigByExperience, validates variation, stores per-context
_preview. Preview context: runExperience forces getPreviewDecision for the target key, all run*
run with enableTracking/enableStorage false + BUCKETING fires suppressed, trackConversion
short-circuits. Zero /track (fetch+beacon) and zero store writes; concurrent non-preview context
unaffected. Fixed latent getData() {}-vs-null bug. Node 4 preview tests + 159 js-sdk suite +
Playwright browser gate (zero-trace on real transport) all green.
Beads: ai-driven-product-dev-t5ir
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dit R1 fix) Thread enableStorage through matchRulesByField -> selectLocations and gate its putData (data-manager.ts) so a preview context running a location-targeted non-target experience writes nothing. Guard Context runCustomSegments/setDefaultSegments/updateVisitorProperties on _preview (no visitor-state persistence on a preview context). Fix JSDoc @returns parse error in parse-preview-param.ts. 4 new storage tests + browser store-write spy green; js-sdk 163 + data 104 + segments 7 + experience 12 suites green. Beads: ai-driven-product-dev-y5gj Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…review (decision-audit R2) selectLocations obtained the visitor's stored locations array by reference from getData (live _bucketedVisitors on the memory path) and push/spliced it in place BEFORE the enableStorage-gated putData -- mutating shared visitor state on a preview context. Now operates on a copy when enableStorage is false (zero change to the normal persist path). Regression test (seed locations, run under preview, assert array unchanged) + 9 preview tests + 164 js-sdk + 104 data (incl golden-vector) + browser gate all green. Beads: ai-driven-product-dev-ehrh Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stale RED comments getConfigByExperience now evicts its cache entry on fetch rejection (guarded against clobbering a newer entry) so a transient failure no longer blocks preview retry for 60s; regression test added. Removed stale RED-phase comments + now-false 'as any' casts from committed preview tests (methods exist on impl+interface). 8 config + 24 api + 104 data + 9 context-preview + 164 js-sdk + browser gate 39 all green. Beads: ai-driven-product-dev-4qqt Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a preview mode feature (qs-02 / SDK-4) to the JS SDK, allowing a Context to be put into a zero-trace preview state for a specific experience and variation. It adds getConfigByExperience to ApiManager with process-wide memoization, supports a debugToken option to force low-cache config fetches, and implements getPreviewDecision in DataManager to bypass standard bucketing and tracking. The review feedback highlights three key improvements: evicting expired entries from the configByExperienceCache to prevent memory leaks, URL-encoding query parameters in getConfigByExperience to avoid query corruption, and shallow-copying storedLocations in DataManager.selectLocations to prevent unintended in-place mutations of stored objects.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…pired preview-config cache entries Adds localeCompare comparators to the two bare string .sort() calls flagged by SonarCloud rule S2871 in api-manager-config-by-experience.tests.ts, and sweeps expired entries from the process-wide configByExperienceCache map on every getConfigByExperience() call so resolved-but-expired entries for distinct experienceIds don't accumulate unbounded in long-running servers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|

Summary
Implements qs-02 Experiment Preview (debug token + preview links) for the Convert JS SDK — an additive feature (no bucketing-contract change, no API/OpenAPI work; the serving primitives
?exp=and?debug_token=already exist server-side).Stacked PR: targets
feat/anchored-bucketing-layout(qs-01), NOTmain.Two capabilities:
(A)
debugTokenconfig option —config.debugToken(string, optional). When set, every config-fetch URL carriesdebug_token=<value>and a forced_conv_low_cache=1(regardless ofnetwork.cacheLevel), so the SDK receives the full QA config with server caches bypassed. The token is never sent to the track endpoint and never logged. No change to experience-running semantics.(B) Per-context experiment preview —
context.setPreview({experienceId, variationId})force-decides one variation, bypassing rules/audiences/segments/locations/environment/status/traffic/stored-decisions/bucketing-hash. Fetches the experience via?exp=when absent from config (memoized process-wide, 60 s TTL). Zero-trace: on a preview context ALL tracking is suppressed at the enqueue source (fetch ANDsendBeacon) and ALL visitor-state writes are disabled; other experiences still evaluate/decide normally. Inert on bad input (warn + normal behavior). Plus a pure exported helperparsePreviewParam(value).Acceptance criteria — all covered by tests
?exp=renders the requested variation/trackvia sendBeacon+fetch, zero DataStore writes on the real UMD bundle)?exp=fetch; TTL refetchparsePreviewParam('123.456')→{experienceId:'123',variationId:'456'}; malformed → nullTest results
Notable engineering notes
enableStorageflag (mirrorsenableTracking) threaded through the bucketing + rule-match + location paths so a preview context performs zero visitor-state writes — chosen over instance-wide suppression to preserve isolation (AC6).?exp=response is never merged into shared config (per-context scratch) — no leak across contexts.selectLocationswrite, and an in-placelocations-array mutation via a by-referencegetData), both with regression tests.Open decisions for reviewer (non-blocking, deliberate)
runExperienceonly; the pluralrunExperiences/runFeature(s)run suppressed (zero-trace) but do not force the target. Safe for the canonical draft-only-via-?exp=target (omitted from shared config). Matters only if the previewed experience is also a shared-config experience rendered via the batch API. Flagging for an explicit call.putSegments(context.ts) seeds segments at construction ifvisitorPropertiesare supplied — beforesetPreview. Outside the preview lifecycle; document that a trace-free preview context should be created without initialvisitorProperties.Test plan
enableStoragegating).feat/anchored-bucketing-layout).🤖 Generated with Claude Code