Skip to content

chore(vue-sdk): add variation composables#1773

Merged
joker23 merged 3 commits into
mainfrom
skz/sdk-2194/migrate-vue-sdk-variation-composables
Jul 6, 2026
Merged

chore(vue-sdk): add variation composables#1773
joker23 merged 3 commits into
mainfrom
skz/sdk-2194/migrate-vue-sdk-variation-composables

Conversation

@joker23

@joker23 joker23 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Note

Medium Risk
New public API on the flag evaluation path affects when impressions fire and how apps render before init; behavior is heavily tested but mistakes could skew analytics or UI gating.

Overview
Adds the Vue SDK’s reactive flag evaluation layer: typed composables for bool/string/number/JSON flags plus matching *VariationDetail helpers, all built on a shared useVariationCore that wires into the existing provider injection.

useVariationCore keeps a readonly ref in sync by calling the client when ready (otherwise defaults or synthetic CLIENT_NOT_READY detail), listening on change:<key>, and re-running when the flag key, context after identify(), or initialization state changes. A batched watch is meant to collapse simultaneous change:<key> and context updates into one evaluation per identify (guarded by tests for duplicate impressions). Listeners are swapped when the key is reactive and removed on scope dispose.

Public exports move from the prior scaffold TODO to composables/index. Tests cover provider/plugin integration, lifecycle, and typed behavior; makeMockClient now tracks handlers in arrays with correct off and sets ready from init status.

Reviewed by Cursor Bugbot for commit dd1d9ce. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

Copy link
Copy Markdown
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 26365 bytes
Compressed size limit: 29000
Uncompressed size: 129044 bytes

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 38747 bytes
Compressed size limit: 39000
Uncompressed size: 212249 bytes

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@launchdarkly/browser size report
This is the brotli compressed size of the ESM build.
Compressed size: 179504 bytes
Compressed size limit: 200000
Uncompressed size: 831427 bytes

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 32027 bytes
Compressed size limit: 34000
Uncompressed size: 114248 bytes

@joker23

joker23 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit d469468. Configure here.

@joker23 joker23 marked this pull request as ready for review June 25, 2026 19:05
@joker23 joker23 requested a review from a team as a code owner June 25, 2026 19:06

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

Comment thread packages/sdk/vue/__tests__/client/composables/useVariation.test.ts
…t.ts

Co-authored-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com>
cursor[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment on lines +48 to +63
let currentKey = toValue(key);
const changeHandler = () => {
update();
};
client.on(`change:${currentKey}`, changeHandler);

// Re-subscribe when the key changes; re-evaluate when the key, context, or readiness changes.
// Both the change-event handler and the context watch may call evaluate() on a single identify().
const stop = watch([() => toValue(key), context, initializedState], ([newKey]) => {
if (newKey !== currentKey) {
client.off(`change:${currentKey}`, changeHandler);
currentKey = newKey;
client.on(`change:${currentKey}`, changeHandler);
}
update();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Flag evaluation fires twice on a single identify, causing duplicate analytics impressions

The flag is evaluated a second time by the context watcher (update() at packages/sdk/vue/src/client/composables/useVariationCore.ts:62) after it was already evaluated synchronously by the change-event handler, so a single identify produces two evaluation calls instead of one.

Impact: Each identify that changes a flag value generates a redundant analytics impression, inflating metrics.

Duplicate evaluation mechanism during identify()

When identify() is called in the base SDK, it emits both change:<key> (synchronously) and a context-change notification. In useVariationCore, these are handled by two independent paths:

  1. The changeHandler (packages/sdk/vue/src/client/composables/useVariationCore.ts:49-51) is registered via client.on('change:<key>', ...) at line 52. It fires synchronously when change:<key> is emitted, calling update()evaluateValue()client.boolVariation() (or whichever typed variant).

  2. The Vue watch (packages/sdk/vue/src/client/composables/useVariationCore.ts:56-63) monitors [key, context, initializedState]. When onContextChange updates context.value (packages/sdk/vue/src/client/provider/LDVueContext.ts:49), the watcher is scheduled. On the next tick it fires and calls update()evaluateValue()client.boolVariation() again.

There is no debounce, guard flag, or coalescing mechanism between these two paths. The comment at line 54-55 acknowledges the overlap but no code prevents the duplicate.

The test at packages/sdk/vue/__tests__/client/composables/useVariation.test.ts:187-209 expects exactly 1 call (toHaveBeenCalledTimes(1)) after emitting both events, but the implementation produces 2 calls.

Prompt for agents
In useVariationCore.ts, the changeHandler (line 49-51, triggered by client.on('change:<key>')) and the Vue watch callback (line 56-63, triggered by context/initializedState changes) both call update() independently. When identify() fires both events, evaluate() is called twice, producing duplicate analytics impressions.

To fix this, introduce a microtask-level coalescing mechanism for the update() call. For example, replace the direct update() calls in both the changeHandler and the watcher with a scheduleUpdate() function that uses a simple dirty flag and queueMicrotask (or Vue's nextTick) to batch multiple triggers into a single evaluation:

  let updatePending = false;
  const scheduleUpdate = () => {
    if (!updatePending) {
      updatePending = true;
      queueMicrotask(() => {
        updatePending = false;
        update();
      });
    }
  };

Then call scheduleUpdate() instead of update() in both the changeHandler and the watch callback. This ensures that even when both events fire in the same tick, only one evaluation occurs.

Alternatively, if synchronous update from the changeHandler is desirable for streaming updates (where no context change follows), you could add a per-tick guard that skips the evaluation in the watcher if the changeHandler already ran during the same microtask.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is stale.

@joker23 joker23 force-pushed the skz/sdk-2194/migrate-vue-sdk-variation-composables branch from 044db00 to dd1d9ce Compare June 30, 2026 16:55
@joker23 joker23 requested a review from kinyoklion June 30, 2026 19:15
@joker23 joker23 merged commit eb17cff into main Jul 6, 2026
53 checks passed
@joker23 joker23 deleted the skz/sdk-2194/migrate-vue-sdk-variation-composables branch July 6, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants