feat: randomize mobile feed ad positions via adJitter#5899
feat: randomize mobile feed ad positions via adJitter#5899
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@claude please review |
|
Claude finished @nimrodkra's task in 3m 27s —— View job Code Review
Overall this is a clean, well-reasoned implementation. The FNV-1a hash is a solid choice for a lightweight deterministic seed, and the clamping invariant ( Issues1. First ad can appear at index 0 — contradicts the PR descriptionWith // useFeed.ts:185
if (index < adStart - safeJitter) { // 2 - 2 = 0 → only guards index < 0
return undefined;
}For 2. Pure utility function lives in a hook file
Minor / NitsTest coverage gap — The "returns undefined before first possible slot" test only asserts on // useFeed.spec.ts:22-26
it('returns undefined for indices before the first possible ad slot', () => {
expect(
getAdSlotIndex({ index: -1, adStart, adRepeat, adJitter, seed }),
).toBeUndefined();
});A more meaningful test would also cover e.g.
Seed stability note — What's solid
|
Add optional `adJitter` to `FeedAdTemplate` so ad positions can vary deterministically per feed while keeping average cadence unchanged. Jitter only activates when the `feed_ad_template` GrowthBook flag sets `adJitter` under the `default` key, so only the mobile/1-column list layout is affected. - Extend FeedAdTemplate with optional adJitter - Pure getAdSlotIndex helper with seeded FNV-1a hash keyed off feedQueryKey - Clamp jitter to floor((adRepeat - 1) / 2) so consecutive ads never overlap or reorder - Unit tests for parity, window bounds, determinism, and clamp Made-with: Cursor
1c7b875 to
13f6f1a
Compare
- Move getAdSlotIndex and hashSeed from useFeed.ts to lib/feed.ts since they are pure, React-free utilities. - Clamp first ad to never land before adStart by using one-sided jitter (0..+adJitter) for n=0; subsequent slots keep symmetric jitter. This matches the documented behavior "first ad in posts 3-5" rather than 1-5. - Co-locate spec next to source (lib/feed.spec.ts) and add tests for the adStart lower bound (no jitter), the adRepeat <= 0 guard, and the first-ad lower bound across many seeds. Made-with: Cursor
| adStart, | ||
| adRepeat, | ||
| adJitter, | ||
| seed: JSON.stringify(feedQueryKey), |
There was a problem hiding this comment.
Why do we need to set a seed here? I think it will position the ad in the same spot for the same user. Double check it
| if (index < adStart) { | ||
| return undefined; | ||
| } | ||
| const safeJitter = Math.max( |
There was a problem hiding this comment.
Looking at this, the implementation limits the jitter based on adRepeat, which means if the repeat is every 3 posts, we can end up having post, post, ad, ad, post, .... this isn't what we want right? basically we want to give dynamic range
Summary
Adds an optional
adJitterknob to the existingfeed_ad_templateGrowthBook flag so mobile ad positions (first ad + gaps) vary deterministically per feed. Desktop is untouched because the flag is already per-breakpoint andadJitteris only set under thedefaultkey at rollout time.Today mobile injects an ad at post 3 and then at a fixed cadence, which feels predictable. With
adJitter: 2ondefault, the first ad lands in posts 3–5 and each subsequent gap varies betweenadRepeat ± 2, keeping average frequency unchanged.Changes
FeedAdTemplategains optionaladJitter?: number.getAdSlotIndexinpackages/shared/src/lib/feed.tswith a seeded FNV-1a hash keyed offfeedQueryKey. Positions are stable across re-renders / pagination but differ per feed / user.getAdinuseFeeduses the helper; deps updated to includeadTemplate?.adJitterandfeedQueryKey.n = 0is one-sided (0..+adJitter) so the first ad can never land beforeadStart; subsequent slots use symmetric jitter (-adJitter..+adJitter).floor((adRepeat - 1) / 2)so consecutive ads can never overlap or reorder, even with misconfigured flag values.adStartlower bound with and without jitter,adRepeat <= 0guard, window bounds per slot, determinism, seed variance, and overlap clamp.Rollout
Only affects mobile when the
feed_ad_templatepayload setsadJitterunder thedefaultkey. Example:{ "default": { "adStart": 2, "adRepeat": 5, "adJitter": 2 }, "tablet": { "adStart": 2, "adRepeat": 5 }, "laptop": { "adStart": 2, "adRepeat": 5 }, "laptopL": { "adStart": 2, "adRepeat": 5 }, "laptopXL": { "adStart": 2, "adRepeat": 5 }, "desktop": { "adStart": 2, "adRepeat": 5 } }Absent
adJittermeans today's behavior — safe to merge before flipping the flag.Test plan
pnpm --filter shared lintpnpm exec jest src/lib/feed.spec.ts(8/8 pass)defaultbreakpoint (adJitter: 0vsadJitter: 2) to measure CTR