Skip to content

fix(exposure): cross-variant tracking for create changes (FT-1879)#5

Merged
joalves merged 2 commits into
mainfrom
fix/FT-1879/create-cross-variant-exposure
Apr 29, 2026
Merged

fix(exposure): cross-variant tracking for create changes (FT-1879)#5
joalves merged 2 commits into
mainfrom
fix/FT-1879/create-cross-variant-exposure

Conversation

@joalves
Copy link
Copy Markdown
Contributor

@joalves joalves commented Apr 29, 2026

Summary

  • Closes FT-1879.
  • create changes with trigger_on_view: true previously fell through the generic selector-tracking branch in ExposureTracker.registerExperiment, so peer variants without the create had nothing observable and never fired exposure when the would-be position entered viewport. This broke cross-variant SRM parity (the same gap that move and delete already solved).
  • Fix mirrors the move/delete pattern: collect every unique (targetSelector, position) pair across all variants' viewport-triggered create changes and drop a 1px invisible positional placeholder via createContainerPlaceholder at each. Variants that do create the element keep their real DOM node — the placeholder coexists harmlessly.
  • Malformed create changes (no targetSelector) are skipped explicitly — DOMManipulatorLite.applyChange returns false for them too, so there's nothing to track and no fallback is needed.
  • Adds five new test cases under 6F: Create Changes: viewport-trigger baseline, peer-variant placeholder exposure, multi-variant different positions, same-position dedup, and malformed-create no-op.
  • Also gitignores .claude/tasks/ and .worktrees/.

Test plan

  • npx jest --testPathPattern="DOMChangesPluginLite" — 7 suites, 259 passed (254 baseline + 5 new), 1 skipped, 0 failures.
  • npx jest --testPathPattern="crossVariantExposure" -t "6F" — all 6F.* tests pass.
  • npm run lint — 0 errors after prettier; 55 pre-existing Unexpected any warnings unchanged.
  • CI green on this branch.

Summary by CodeRabbit

  • New Features

    • Cross‑variant exposure tracking for create operations triggered by viewport visibility, ensuring consistent exposure across experiment variants.
  • Documentation

    • Added an implementation plan describing the approach and validation scenarios for cross‑variant exposure tracking.
  • Tests

    • Expanded test coverage for cross‑variant create exposure, peer‑variant behaviour, deduplication, malformed creates and late‑mount scenarios.
  • Chores

    • Updated .gitignore with additional ignore patterns.

create changes with trigger_on_view previously fell through the generic
selector-tracking branch in ExposureTracker, so peer variants without the
create had nothing observable and never fired exposure when the would-be
position entered viewport. Mirror the move/delete pattern: collect every
unique (targetSelector, position) pair across all variants' viewport-
triggered create changes and drop an invisible positional placeholder at
each. Malformed creates (no targetSelector) are skipped explicitly --
the manipulator can't apply them either.

Also gitignore .claude/tasks/ and .worktrees/.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Walkthrough

The PR adds two ignore patterns to .gitignore, introduces a new markdown plan for cross-variant exposure of create changes, updates src/core/ExposureTracker.ts to collect unique (targetSelector, position) pairs across variants and insert invisible positional placeholders (skipping malformed creates), and expands tests in src/core/__tests__/DOMChangesPluginLite.crossVariantExposure.test.ts to cover peer-variant exposure, multi-position handling, deduplication, malformed creates, SPA late-mounts, and URL-filtered exposures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through variants, quiet and spry,
Planting tiny placeholders low and high,
Waiting for viewports to wink and see,
So creates across branches bloom properly,
A joyful nibble — exposure set free! 🌱

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and accurately summarises the primary change: implementing cross-variant tracking for create changes to fix an exposure-tracking gap (FT-1879).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/FT-1879/create-cross-variant-exposure

Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/ExposureTracker.ts`:
- Around line 195-209: createPositions loop calls createContainerPlaceholder
once and returns early if targetSelector isn't present, so delayed-mounted
targets never get placeholders; update the logic around createPositions /
createContainerPlaceholder to register a fallback that watches for the target to
appear (e.g., via MutationObserver or a small retry/observer registry) and
inserts the same placeholder when the target is added to the DOM; reference
createPositions, createContainerPlaceholder, experimentName, and
pos.targetSelector/pos.position in your change and ensure the observer/registry
cleans itself up after insertion to avoid leaks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aef76f1a-57e8-453e-b515-fee0cdfaaba5

📥 Commits

Reviewing files that changed from the base of the PR and between cea244b and bf443e8.

📒 Files selected for processing (4)
  • .gitignore
  • docs/superpowers/plans/2026-04-29-create-cross-variant-exposure.md
  • src/core/ExposureTracker.ts
  • src/core/__tests__/DOMChangesPluginLite.crossVariantExposure.test.ts

Comment thread src/core/ExposureTracker.ts
…g (FT-1879)

Addresses review feedback on PR #5:

- Delayed-mount fallback (CodeRabbit, code-reviewer, test-analyzer,
  silent-failure-hunter all flagged this): when targetSelector is not in
  the DOM at registration time (SPA late-mount), createContainerPlaceholder
  now returns false and the create-positions loop falls back to adding the
  targetSelector to viewportSelectors, so the existing MutationObserver
  picks up exposure when the target eventually appears.

- Collision-safe key: replace the "${target}|${position}" composite map key
  with a nested Map<targetSelector, Set<position>> so CSS selectors that
  legally contain '|' (namespace, attribute selectors) cannot collide.

- Debug log when a malformed create (no targetSelector) is skipped, so the
  silent skip can be discovered when debug=true.

Test additions:
- 6F3 extended with cleanup verification (placeholder removed, no re-fire).
- 6F4 second test exercises the OTHER (header) placeholder.
- 6F6 second test confirms a malformed create does not poison registration
  of well-formed sibling changes in the same variant.
- 6F7 (new): delayed-mount targetSelector — exposure fires after target is
  appended later via MutationObserver.
- 6F8 (new): URL-filter combined with viewport-create cross-variant
  tracking, both v0 and v1 users.

Suite: 263 passed (was 259), 1 skipped, 0 failures, 0 lint errors.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/core/__tests__/DOMChangesPluginLite.crossVariantExposure.test.ts (1)

4093-4516: Consider extracting shared create change fixtures to reduce duplication.

The repeated inline experiment payloads make this section harder to maintain and easier to drift. A small builder/helper for create changes would keep intent clearer and reduce copy/paste risk.

♻️ Example refactor sketch
+const createChange = (overrides: Partial<any> = {}) => ({
+  selector: '',
+  type: 'create',
+  element: '<div class="new"></div>',
+  targetSelector: '.container',
+  position: 'lastChild',
+  trigger_on_view: true,
+  ...overrides,
+});
+
+const createExperiment = (name: string, variantChanges: any[]) => ({
+  name,
+  variants: [{ variables: { __dom_changes: [] } }, { variables: { __dom_changes: variantChanges } }],
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/__tests__/DOMChangesPluginLite.crossVariantExposure.test.ts` around
lines 4093 - 4516, Extract the repeated "create" change fixtures into a small
helper (e.g., makeCreateChange or buildCreateExperiment) and use it in the tests
that construct ExperimentData (tests referencing test_6f3_peer_variant,
test_6f4_multi_position*, test_6f5_dedup, test_6f6_*, test_6f7_delayed_target,
test_6f8_url_filter) so the tests call the helper instead of embedding identical
__dom_changes payloads; add the helper near createTreatmentTracker in the test
file and have it return either the __dom_changes entry or a full ExperimentData
variant payload (accepting params like element, targetSelector, position,
trigger_on_view, optional urlFilter) and update each test to call that helper
when building the experiment used by DOMChangesPluginLite.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/core/__tests__/DOMChangesPluginLite.crossVariantExposure.test.ts`:
- Around line 4093-4516: Extract the repeated "create" change fixtures into a
small helper (e.g., makeCreateChange or buildCreateExperiment) and use it in the
tests that construct ExperimentData (tests referencing test_6f3_peer_variant,
test_6f4_multi_position*, test_6f5_dedup, test_6f6_*, test_6f7_delayed_target,
test_6f8_url_filter) so the tests call the helper instead of embedding identical
__dom_changes payloads; add the helper near createTreatmentTracker in the test
file and have it return either the __dom_changes entry or a full ExperimentData
variant payload (accepting params like element, targetSelector, position,
trigger_on_view, optional urlFilter) and update each test to call that helper
when building the experiment used by DOMChangesPluginLite.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ba90ff65-7280-4f4d-b137-6741fb490df7

📥 Commits

Reviewing files that changed from the base of the PR and between bf443e8 and 63f1851.

📒 Files selected for processing (2)
  • src/core/ExposureTracker.ts
  • src/core/__tests__/DOMChangesPluginLite.crossVariantExposure.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/ExposureTracker.ts

@joalves joalves merged commit 17f5c89 into main Apr 29, 2026
2 checks passed
@joalves joalves deleted the fix/FT-1879/create-cross-variant-exposure branch April 29, 2026 21:54
joalves added a commit that referenced this pull request Apr 30, 2026
Follow-up to #5: extract a buildCreateChange(overrides) helper near the imports of the cross-variant exposure test file and route 6F2-6F8 through it. Each test now declares only the fields that vary. No behavioural change; net -55 lines.

Closes the CodeRabbit nitpick on #5.
@joalves
Copy link
Copy Markdown
Contributor Author

joalves commented Apr 30, 2026

Followed up on the buildCreateChange nitpick in #6 — merged as 3d9e9f9. Net -55 lines across 6F2-6F8.

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.

1 participant