Skip to content

feat(ad-intent): UI v4 Part B — recommendedAction, landingPageMetrics, whatsLikelyHappening pass-through#2678

Open
danieljchuser wants to merge 10 commits into
mainfrom
feat/ad-intent-mismatch-ui-v4
Open

feat(ad-intent): UI v4 Part B — recommendedAction, landingPageMetrics, whatsLikelyHappening pass-through#2678
danieljchuser wants to merge 10 commits into
mainfrom
feat/ad-intent-mismatch-ui-v4

Conversation

@danieljchuser

Copy link
Copy Markdown
Collaborator

Summary

Part B (audit-worker) of the Ad Intent Mismatch UI v4 spec (adobe/mysticat-architecture#149). Adds three new fields to the ad-intent-mismatch opportunity.data and persists one supporting metric — all additive, no Mystique or UI dependency to start populating recommendedAction + landingPageMetrics.

  • recommendedAction — derived in the mapper from poorly-aligned (overallAlignmentScore === 'poor', non-failed) clusters: an exclude action with deduped totalKeywords/totalSearchVolume and a per-cluster "why excluded" reason (keywordToPageGap.explanation → gapDescription → null). null when no poor clusters.
  • landingPageMetrics{ bounceRate, engagedScrollRate, paidTrafficShare } matched from the audit's predominantlyPaidPages by normalized URL (mapper gains a 4th auditResult arg, passed by the guidance handler). null when no match; paidTrafficShare null for pre-change rows.
  • paidTrafficShare persisted per page in paidKeywordOptimizerRunner (previously computed then discarded).
  • whatsLikelyHappening — verbatim pass-through of the Mystique SQS field (?? null); a no-op until Mystique emits it, then carried through with no further change.
  • normalizeUrl extracted to a shared utils.js; field-population log (snake_case booleans) after opportunity creation for post-deploy Coralogix verification (spec M9).

Strictly additive: no existing opportunity.data or top-level field renamed/removed/retyped.

Test Plan

  • Full suite: 8637 passing, 0 failing, 100% lines/branches/statements (npm test)
  • Lint clean (npm run lint)
  • TDD per task; new branch cases covered (reason chain incl. missing keywordToPageGap, traffic ?? 0, keywords || [] fallback, URL www-normalization, paidTrafficShare-absent)
  • Two adversarial review rounds on the plan + per-task spec/quality review + final whole-branch review

Plan: docs/plans/2026-06-16-ad-intent-mismatch-ui-v4-audit-worker.md
Spec: adobe/mysticat-architecture#149

🤖 Generated with Claude Code

danieljchuser and others added 9 commits June 16, 2026 08:14
TDD implementation plan for Part B of the ad-intent-mismatch UI v4 spec
(adobe/mysticat-architecture#149): persist paidTrafficShare; derive
recommendedAction; thread landingPageMetrics via a 4th mapper arg; pass
whatsLikelyHappening through; field-population observability log.

7 bite-sized tasks with complete code, grounded in the real source/tests.
Incorporates two adversarial plan-review rounds (gaps #3/#4, drifts #1/#2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…4th mapper arg

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eploy verification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@danieljchuser danieljchuser self-assigned this Jun 16, 2026
@danieljchuser danieljchuser added ai-native enhancement New feature or request labels Jun 16, 2026
@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@danieljchuser danieljchuser left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

AI Review

Hey @danieljchuser,

Verdict: Comment - no blocking issues. Clean, additive, well-tested change; the notes below are all optional.
Changes: adds whatsLikelyHappening / recommendedAction / landingPageMetrics to the ad-intent-mismatch opportunity.data, persists paidTrafficShare per predominantly-paid page, and extracts normalizeUrl into a shared utils.js (9 files; ~124 production LOC, test-to-code ratio 2.86).

No Critical or Important findings. The one direct dereference (auditResult.predominantlyPaidPages) is provably safe: the sole production caller in guidance-handler.js early-returns when auditResult is falsy before reaching the mapper, and the inline comment documents that contract. Every new branch (the ?? null / ?? 0 arms, c.keywords || [], the reason chain, the recommended_action_clusters log branch) has a matching test.

Non-blocking (6): doc placement, test parity, and small consistency notes
  • suggestion (docs): the repo CLAUDE.md literally says significant new functionality should add a spec under docs/specs/; this PR ships a plan in docs/plans/ and the controlling spec lives cross-repo at adobe/mysticat-architecture#149. That split matches the team information hierarchy (cross-repo platform specs in mysticat-architecture, repo-specific plans in the repo) and how prior same-area PRs shipped, so it is not a blocker - but consider either a thin docs/specs/ pointer or reconciling the CLAUDE.md wording so the rule and practice agree. docs/plans/2026-06-16-ad-intent-mismatch-ui-v4-audit-worker.md
  • suggestion (docs): the EXCLUDE_ALIGNMENT_SCORES ({poor}) vs MISALIGNED_SCORES ({poor, fair}) divergence is a real product trade-off. Reviewers split on whether it needs an ADR under docs/decisions/ or whether the inline comment plus the cross-repo spec are sufficient. Your call - flagging so the decision is explicit. src/paid-keyword-optimizer/guidance-opportunity-mapper.js (EXCLUDE_ALIGNMENT_SCORES)
  • nit: the normalizeUrl relocation into utils.test.js dropped the old explicit empty-string assertion (normalizeUrl('') === ''). c8 coverage is unaffected (the empty string hits the same catch arm as 'not a url'), but the behavioral case is no longer asserted - re-add one line for parity. test/audits/paid-keyword-optimizer/utils.test.js
  • nit: landingPageMetrics guards paidTrafficShare ?? null but copies bounceRate / engagedScrollRate raw, so a row missing those two would persist undefined while the third normalizes to null. Benign today (the runner always computes all three); either guard all three for a uniform wire contract or add a one-line comment that only paidTrafficShare is back-compat-optional. src/paid-keyword-optimizer/guidance-opportunity-mapper.js (landingPageMetrics block)
  • suggestion: the exported mapToKeywordOptimizerOpportunity now throws TypeError if a future caller passes undefined as the 4th arg. Safe at the only current caller, so optional; note that switching to auditResult?.predominantlyPaidPages || [] would add a new branch the strict 100% gate then needs a test for, which is presumably why it was written this way. src/paid-keyword-optimizer/guidance-opportunity-mapper.js (signature)
  • suggestion: hoist normalizeUrl(url) out of the .find() predicate (it is loop-invariant); minor readability/clarity, not a real perf concern given the small list. src/paid-keyword-optimizer/guidance-opportunity-mapper.js (matchedPage find)

Out of scope, worth tracking

  • The downstream UI should escape whatsLikelyHappening and recommendedAction.clusters[].reason on render - they are upstream-controlled (Mystique) free text persisted verbatim. Producer-side sanitization here is not warranted (non-rendering JSON layer), so this belongs in the consuming UI repo, not this PR.
  • The cross-repo wire contract for these three fields now lives implicitly in this mapper plus adobe/mysticat-architecture#149. As Mystique and the UI adopt the fields, a shared schema (or a schema-validation test) would prevent producer/consumer drift.

@github-actions

Copy link
Copy Markdown
Contributor

This PR will trigger a minor release when merged.

@MysticatBot MysticatBot 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.

Hey @danieljchuser,

Verdict: Approve - clean, additive, well-tested change with no blocking issues.
Changes: adds whatsLikelyHappening, recommendedAction, and landingPageMetrics to the ad-intent-mismatch opportunity.data, persists paidTrafficShare per predominantly-paid page, and extracts normalizeUrl to a shared utils.js (9 files).

Note: CI checks are currently pending - verify before merge.

Non-blocking (5): minor consistency and edge-case notes
  • nit: normalizeUrl(url) is loop-invariant inside the .find() predicate - hoist to a local const normalizedUrl = normalizeUrl(url) for clarity - src/paid-keyword-optimizer/guidance-opportunity-mapper.js:183
  • nit: bounceRate and engagedScrollRate are copied raw while paidTrafficShare gets ?? null - applying the same guard to all three would make the wire contract uniform for pre-existing rows - src/paid-keyword-optimizer/guidance-opportunity-mapper.js:185
  • nit: k.traffic ?? 0 handles null/undefined but NaN or negative values from the SEO provider would propagate into totalSearchVolume - consider Number(k.traffic) || 0 or a floor at 0 - src/paid-keyword-optimizer/guidance-opportunity-mapper.js:43
  • nit: the old handler.test.js asserted normalizeUrl('') === '' explicitly; the new utils.test.js exercises the catch path via 'not a url' but dropped the empty-string boundary assertion - test/audits/paid-keyword-optimizer/utils.test.js
  • suggestion: CLAUDE.md says "Add a spec to /docs/specs/" but team practice places platform specs cross-repo and local plans in docs/plans/ - consider reconciling the wording or adding a thin pointer under docs/specs/ so rule and practice agree - CLAUDE.md (Documentation Requirements)

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 1m 6s | Cost: $2.76 | Commit: 95ed0256416b33907ab394b21044ee91396513fe
If this code review was useful, please react with 👍. Otherwise, react with 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants