feat(hijacking): cover-art strip arms for the next experiment - #6492
Open
tsahimatsliah wants to merge 18 commits into
Open
feat(hijacking): cover-art strip arms for the next experiment#6492tsahimatsliah wants to merge 18 commits into
tsahimatsliah wants to merge 18 commits into
Conversation
Two new arms on hijacking_variants3, both reusing the daily.dev homepage's closing artwork with centered copy and a Get started / Log in pair that hands off to the webapp onboarding flow: - cover: sticky at the top of the feed, cards scroll behind it - cover_bottom: the same card floating fixed at the viewport bottom Banner height is a controlled variable in the header-ad-impression experiment, so the card keeps two invisible in-flow sizers that replicate the control strip's exact geometry (its cat artwork and its text block); all visible content overlays them absolutely. Verified pixel-identical to the control at mobile/desktop/wide widths. Also adds a Storybook page (Extension/HijackingStrip) rendering every arm of the flag over a mock feed, with an anonymous boot mode for ExtensionProviders so logged-out arms render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Stacked, the control's 12.5rem media panel sat below the overlaid copy as dead space. The vertical layout now keeps only the text sizer (206px, down from 447px); tablet and up still mirror the control's full row geometry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review of the first pass turned up problems that would have invalidated the experiment: - The arms rendered through the `shortcuts` slot, which FeedContainer puts inside its search <header>. A sticky child there has almost no travel, so the pinning never happened outside Storybook. The cover arms now render through `topBanner`, whose column spans the feed — the same slot ExtensionSignInStrip already pins from. - The height sizer replicated the CTA arm rather than the control, leaving the strip 44px taller than the control on mobile. It now reproduces the control's own text block (its copy and a real Button) and reserves the media panel with the control artwork's intrinsic ratio, landing within the same 1px the CTA arm already carries. - Sign-back and onboarding states dropped the arms back into the top slot, so a remembered visitor watched the strip jump after storage resolved. Placement is now shared across every state of an arm. - The bottom arm sat at z-3, under the sidebar (z-70) and over the scroll-to-top button (z-2). It now uses the shared bottom-banner primitive: z-modal, centered, clear of the button's corner. - The sizer fetched the control's cat artwork (~88KB) only to hide it. - The storybook boot mock was mutated during render and never restored, leaking an anonymous user into later stories; it moves to a story `beforeEach` with a per-mount query client. - The arm dispatch is derived from the hero map, so a future arm can't be added to the enum and map yet silently fall through to the control. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The z-index fix had capped the pinned card at 40rem and centered it, so it no longer lined up with the feed. It now spans the viewport gutters up to the feed's own max width, measuring identical to the cards field (960px at 160-1120 on a 1280 viewport). The cap is an inline style: an arbitrary `max-w-*` class from this package is not guaranteed to reach the compiled bundle, and a silently dropped one had already collapsed the card once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both arms now span the feed column edge to edge, matching the cards exactly at every breakpoint (measured identical width and both edges at 375/656/900/1280). The top arm dropped the control's 16px horizontal inset. The bottom arm stopped being `fixed`: a fixed element sizes against the viewport, so it was 16px wider than the column below 1280 and could not track it at all. It now renders after the feed via MainFeedLayout's children, where `sticky bottom` pins it to the viewport while it inherits the column's width — and where it no longer needs z-modal to clear the sidebar or a carve-out for the scroll-to-top button. `sticky bottom` can only pull an element up into view, which is why the arm has to render after the feed rather than above it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MainFeedPage's MainFeedLayout lost its showDailyHome branch upstream; the feedTrailing children carry over onto the unconditional render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MainLayout renders `topBanner` only inside its `sidebarOwnsHeader` branch, and that requires `isV2` — so the slot exists only under the layout_v2 experiment. The top cover arm rendered there, which meant it would have shown to layout_v2 users only and silently to nobody else, confounding the two experiments. It now goes through a new `feedTopContent` pass-through on MainFeedLayout, landing in FeedContainer's `topContent` inside the feed column: present in every layout branch, correct width, and a full-height container for the sticky travel the arm needs. The bottom arm was never affected — it renders through MainFeedLayout's children, which both branches render. `topContent` still falls back to the reading-reminder hero when neither this nor the chips row is set. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…k's subject Two problems visible only in the real extension: - The global header is `fixed top-0 h-14 laptop:h-16` at z-header, so a `sticky top-0` strip pinned underneath it and had its headline clipped. The top arm now offsets by the header's height, and only when that header is on screen — the v2 layout hides it and gives the sidebar the job. - The cover artwork was anchored at 35% vertically, which reads fine in a tall frame but crops the dog and the person out entirely once the strip is as wide and short as the real feed column. Anchored low instead so the subject stays in frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The arbitrary `object-[50%_54%]` class was not emitted for this package, so the crop silently fell back to centre and dropped the artwork's subject. Same failure mode as the width cap: inline it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e the feed Two problems the real extension surfaced: - A feed card's avatar painted over the strip. The strip is a deliberate interrupt, so it now sits at z-rank rather than z-3 — above card content, still below the sidebar, header and modals. - The dog and the person kept disappearing. `object-cover` scales the art by the card's width, so where the pair lands depends on how wide the card is: the anchor that frames them at 958px is 49.5%, at 1900px it is 61.5%. Any fixed percentage loses them at some width, which is what happened. The crop is now measured from the rendered box and re-measured on resize, holding the pair 4px under the CTA row at every width. The pair is taller than the space under the buttons, so their lower half stays cropped by the card edge — the strip would have to grow to fit them whole, and its height is fixed by the experiment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The pinned arm sat flush against the fixed header. It now clears it by the header's height plus a small gap, so the card reads as floating over the feed instead of welded to the chrome. The offset is inline because it depends on the breakpoint (3.5rem header on mobile, 4rem on laptop) and on whether the header is on screen at all — the v2 layout hides it. - The centred copy is lifted so the band under the CTA belongs to the artwork. The dog and the person now show 54px rather than 36px at the common width; they were being squeezed out by copy sitting too low. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The offset was a constant matching the laptop header (4rem). On phones and tablets the header stacks a nav row under the logo — roughly 95px — so the strip pinned underneath it and the headline was cut off, which is what the constant could never handle. The offset is now measured from whatever is actually parked at the top of the viewport, and re-measured on resize and scroll. Verified against simulated headers: 95px chrome pins the card at 103px, 56px chrome at 64px, and with no chrome it keeps just the 8px floating gap. Measured synchronously rather than on an animation frame so the first paint lands on the right offset; React drops the update when the value is unchanged, so the scroll listener stays cheap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On phones and tablets MainLayoutHeader returns <FeedNav />, whose root is a sticky <div> rather than a <header>. The offset lookup only matched header, nav and role=banner, so it measured nothing there: the strip pinned 8px from the top and FeedNav, sitting on the z-header layer, covered it — the strip appeared to have a sliver of padding and then slid behind the header on scroll. What both chromes share is that layer, so the lookup now matches it too. Verified against a simulated FeedNav: 95px of sticky chrome pins the card at 103px, and it returns to the bare 8px gap once that chrome is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stacked, the copy is taller than the control's row geometry, so laying it out absolutely over that geometry made it overflow the card and clip its own heading against the top edge. The copy now sits in flow below tablet, giving the card its height with an even 20px on every side, and goes back to overlaying the control's geometry from tablet up — which is the width range where banner height is the experiment's controlled variable. Desktop is unchanged at 177.59px against the control's 176.59px. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The control reaches the page through the `shortcuts` slot, which MainFeedPage drops under the v2 layout, while the cover arms render from the feed column, which every layout keeps. A v2 user in the control therefore saw no strip at all while one in a cover arm saw the new card — the comparison would have been "nothing" against "a strip" rather than one design against another, flattering the new arms. The flag is no longer evaluated for those users, so they are not enrolled and no arm renders for them. A test pins this: enrollment must not happen where the control cannot show. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five throwaway extension builds were swept into earlier commits by `git add -A`. They are local review artifacts, not part of the change, and ignoring them keeps the next one out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Two new arms on the extension new-tab hijacking strip, built from the daily.dev homepage's closing artwork with centered copy ("Start discovering what's next.") and a Get started → / Log in pair that hands off to the webapp onboarding flow (same OAuth-origin constraint as the
ctaarm):cover— pinned to the top of the feed; cards scroll behind it.cover_bottom— the same card pinned to the bottom of the viewport.Where each arm renders
default/cta/authshortcuts(unchanged)coverfeedTopContent→FeedContainer'stopContentcover_bottomMainFeedLayoutchildren)sticky bottomcan only pull an element up into view, so it must render after the feedMainLayout'stopBannerwas tried first and is not usable: it renders only inside thesidebarOwnsHeaderbranch, which requiresisV2.Pinning
The pinned arm measures whatever chrome is parked at the top of the viewport and clears it, plus an 8px gap so it reads as floating. The offset can't be a constant: the chrome is a one-row
<header>on laptop, a two-row sticky<div>(FeedNav) on phone and tablet, and absent under v2. Verified against simulated chrome — 95px pins the card at 103px, 56px at 64px, none at 8px.Width and height
Both arms span the feed column edge to edge, measured identical to the cards grid at 375 / 656 / 900 / 1280.
Banner height is a controlled variable in the header-ad-impression experiment (#6315). From tablet up, an invisible in-flow block reproduces the control's row geometry — its copy plus a real
Button, and a media panel reserved by the control artwork's intrinsic ratio so no image is fetched to hold space:The +1px is structural: the control is flush with the feed and carries a top border only, the cover card is bordered on all four sides.
Below tablet the arms are deliberately shorter and size to their own copy (even 20px padding all round) — stacked, the control's geometry puts a band of empty artwork under the copy, and laying the copy over it clipped the heading. So mobile height is not held constant and should be read that way.
Extras:
cover-hero-domeutility (pepper-90 radial viacolor-mix) for copy legibility over the artwork.object-coverscales by width, so the correct anchor runs from 49.5% at 958px to 61.5% at 1900px. It holds the dog and the person just under the CTA at any width.beforeEach.pnpm --filter extension test(lint + 54 tests) ✅ ·@dailydotdev/shared(2196 tests) ✅ ·typecheck-strict-changed✅Known gaps
z-headerlayer. If that class is renamed, the offset silently falls back to the bare 8px gap.Events
No new events — the arms reuse the existing
hijackingimpression/click events (SignupButton/LoginButtontarget types), exactly as the current arms do.Experiment
Two new values (
cover,cover_bottom) on the existinghijacking_variants3flag. The committed default staysdefault(control); the arms only serve once the experiment is configured in GrowthBook.Enrollment is scoped so every arm renders for the same population. The control reaches the page through the
shortcutsslot, whichMainFeedPagedrops under the v2 layout, while the cover arms render from the feed column, which every layout keeps. Enrolling v2 users would have compared "no strip" against "a strip" and flattered the new arms, so the flag is not evaluated for them and no arm renders there. A test pins this.Consequently no
layout_v2targeting rule is needed — those users are excluded in code, and excluded from enrollment rather than merely from the UI, so they do not dilute the split.Important
Please do not merge the PR until the experiment enrolment is approved (message pending in #experiments).
🤖 Generated with Claude Code
Preview domain
https://feat-hijacking-cover-strip.preview.app.daily.dev