Skip to content

feat(hijacking): cover-art strip arms for the next experiment - #6492

Open
tsahimatsliah wants to merge 18 commits into
mainfrom
feat/hijacking-cover-strip
Open

feat(hijacking): cover-art strip arms for the next experiment#6492
tsahimatsliah wants to merge 18 commits into
mainfrom
feat/hijacking-cover-strip

Conversation

@tsahimatsliah

@tsahimatsliah tsahimatsliah commented Aug 18, 2026

Copy link
Copy Markdown
Member

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 cta arm):

  • 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

Arm Slot Why
default / cta / auth shortcuts (unchanged) in-flow strip, no pinning
cover feedTopContentFeedContainer's topContent inside the feed column, so it has the feed's width and a full-height container for sticky travel
cover_bottom after the feed (MainFeedLayout children) sticky bottom can only pull an element up into view, so it must render after the feed

MainLayout's topBanner was tried first and is not usable: it renders only inside the sidebarOwnsHeader branch, which requires isV2.

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:

Viewport Control Cover arms Δ
656 193 194 +1
900 176.59 177.59 +1
1020 176.59 177.59 +1
1280 176.59 177.59 +1
1440 194.09 195.09 +1

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:

  • New cover-hero-dome utility (pepper-90 radial via color-mix) for copy legibility over the artwork.
  • The artwork's subject anchor is measured, not fixed: object-cover scales 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.
  • Storybook page Extension/HijackingStrip: one story per arm over a scrollable mock feed, each rendering on the side of the feed it uses in production, booting anonymous through a story beforeEach.

pnpm --filter extension test (lint + 54 tests) ✅ · @dailydotdev/shared (2196 tests) ✅ · typecheck-strict-changed

Known gaps

  • Pinning and geometry are verified in Storybook and in a side-loaded build of this branch; jsdom has no layout, so the tests assert the positioning contract only.
  • The cover artwork is served at its 2072px source. Its Cloudinary URL is signed, so a width cap can't be appended — it needs a re-upload or re-sign to serve smaller.
  • The chrome lookup matches on the z-header layer. If that class is renamed, the offset silently falls back to the bare 8px gap.

Events

No new events — the arms reuse the existing hijacking impression/click events (SignupButton/LoginButton target types), exactly as the current arms do.

Experiment

Two new values (cover, cover_bottom) on the existing hijacking_variants3 flag. The committed default stays default (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 shortcuts slot, which MainFeedPage drops 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_v2 targeting 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

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>
@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
daily-webapp Ready Ready Preview Aug 19, 2026 8:39am
storybook Building Building Preview Aug 19, 2026 8:39am

Request Review

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>
tsahimatsliah and others added 2 commits August 19, 2026 01:13
…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>
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