Skip to content

Hide native Feast recipe nudge from signed-in users with no Braze banner - #16711

Open
andresilva-guardian wants to merge 2 commits into
mainfrom
afs/hide-native-feast-nudge-to-signed-in-users
Open

andresilva-guardian wants to merge 2 commits into
mainfrom
afs/hide-native-feast-nudge-to-signed-in-users

Conversation

@andresilva-guardian

@andresilva-guardian andresilva-guardian commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

What does this change?

For the Feast contextual recipe nudge (FeastContextualNudge), signed-in readers who are not targeted by any Braze Canvas for that placement now see nothing, instead of falling back to the generic native "Download the app" card.

Signed-out/pending readers are unaffected: since Braze targeting doesn't apply to them at all, they continue to see the native fallback card as before.

CLS risk introduced by this, and how it's mitigated

The reserved-height placeholder (nudgeMinHeightStyles + nudgeSpacingStyles) that's shown while waiting to decide what to render was originally CLS-safe by design: it always got replaced by something of matching height — either a Braze banner or the native card. This change introduces a third possible outcome ("nothing"), which breaks that invariant: when it resolves, the reserved box collapses to zero height, shifting surrounding content.

This can't be eliminated entirely — the "no banner" decision is inherently asynchronous (it depends on Braze), so we can't know before first paint whether a signed-in reader will end up seeing nothing. What we can do is shrink the window during which the reserved box sits on screen before collapsing:

  • Previously, this decision waited on the same isRecipeSaved ("saved from web") fetch used for the Braze banner's context — a fetch that has nothing to do with whether a banner exists, but could take up to 2s (SAVED_FROM_WEB_TIMEOUT_MS) to settle.
  • Now, the "no banner" decision is based purely on Braze's own load state (isLoading, newly exposed from useBraze), so it resolves as soon as Braze itself is ready — independently of, and typically much faster than, the saved-from-web fetch.

Expected result: the placeholder-to-nothing collapse still happens for signed-in readers with no Braze banner, but the window in which the empty placeholder is visible beforehand is minimised rather than tied to a fetch that can take up to 2s. In practice, since other Braze-consuming components on the page may share the same SWR cache key, isLoading can even already be false by the time this component hydrates.

Why?

The native fallback card was originally meant as a generic "Braze isn't available/loaded yet" safety net. However, for signed-in readers, having no eligible Braze banner for a placement means Braze itself has decided (via Canvas targeting) that this reader shouldn't see the nudge. Showing the generic native card in that case undermines that targeting decision and shows a non-personalised nudge to readers who were deliberately excluded.

How has this change been tested?

  • Updated unit tests in FeastContextualNudge.island.test.tsx:
    • a signed-in reader with no eligible Braze banner renders nothing.
    • a signed-in reader is held on the reserved-height placeholder while Braze is still loading (isLoading: true), rather than prematurely resolving to "nothing".
  • Existing tests covering the Braze banner render path, stale-placement fallback, and signed-out fallback behaviour still pass unchanged.
  • useBraze.ts's new isLoading field is additive; other consumers (SlotBodyEnd, StickyBottomBanner, BrazeMessaging) don't destructure it and their existing tests still pass unchanged.

Previously, a signed-in reader who wasn't targeted by any Braze Canvas
for a FeastContextualNudge placement would still see the generic
native "Download the app" fallback card. Since Braze targeting is the
mechanism for deciding who should see this nudge, a signed-in reader
with no eligible banner should see nothing rather than a
non-personalised fallback. Signed-out/pending readers aren't
Braze-targetable at all, so they still see the native fallback.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Hello 👋! When you're ready to run Chromatic, please apply the run_chromatic label to this PR.

You will need to reapply the label each time you want to run Chromatic.

Click here to see the Chromatic project.

@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

🚀 Image pushed to AWS ECR

Image digest: sha256:2bb638dfe48fb18d3bb32dc63ddb16303419ab7f904bc8c0469153c5cb25cb83

🐛 Run the image locally

The following can be used to run the image locally:

# Refer to image using the immutable digest. Find alternatives below.
IMAGE_IDENTIFIER="@sha256:2bb638dfe48fb18d3bb32dc63ddb16303419ab7f904bc8c0469153c5cb25cb83"

# Refer to image using branch tag
# IMAGE_IDENTIFIER=":branch-afs-hide-native-feast-nudge-to-signed-in-users"

# Refer to image using build tag
# IMAGE_IDENTIFIER=":build-30984"

# Refer to image via the GitHub commit SHA tag
# IMAGE_IDENTIFIER=":sha-ac5e4f448431e2d1aadc88277a084b2ee60f520a"

# Set environment variables for the AWS CLI
AWS_PROFILE="<A_PROFILE_FROM_JANUS>"
AWS_DEFAULT_REGION="eu-west-1"

IMAGE_ACCOUNT_ID=$(aws ssm get-parameter --name /organisation/accounts/artifacts --query "Parameter.Value" --output text)
REGISTRY="${IMAGE_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com"
IMAGE="${REGISTRY}/guardian/dotcom-rendering${IMAGE_IDENTIFIER}"

# Login to AWS ECR https://docs.aws.amazon.com/AmazonECR/latest/userguide/registry_auth.html
aws ecr get-login-password | docker login --username AWS --password-stdin $REGISTRY

# Pull the image
docker pull $IMAGE

# Run the image. You'll likely need to set additional flags. See https://docs.docker.com/reference/cli/docker/container/run.
docker run $IMAGE

From guardian/actions-publish-image.

@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

…eaders

Deciding whether to hide the nudge for a signed-in reader who isn't
targeted by any Braze Canvas previously waited on the same
isRecipeSaved (saved-from-web) fetch used for the Braze banner
context. That fetch is unrelated to whether a banner exists at all, so
the reserved-height placeholder could sit on screen for up to 2s and
then collapse to nothing once resolved, causing a visible layout
shift.

Base the 'no banner' decision on Braze's own load state instead
(exposed as isLoading from useBraze, derived from SWR's data/error
since this project's SWR version predates the built-in isLoading
flag). This lets the decision resolve as soon as Braze itself is
ready, independently of the saved-from-web fetch, shortening the
window during which the reserved placeholder is visible before
collapsing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@andresilva-guardian andresilva-guardian added the feature Departmental tracking: work on a new feature label Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Departmental tracking: work on a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant