Skip to content

feat(cards): card_save_on_hover experiment — bookmark in the card header, bigger bar - #6836

Merged
tsahimatsliah merged 11 commits into
mainfrom
feed-card-save-on-hover-dailydotdev
Oct 11, 2026
Merged

tsahimatsliah merged 11 commits into
mainfrom
feed-card-save-on-hover-dailydotdev

Conversation

@tsahimatsliah

@tsahimatsliah tsahimatsliah commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Changes

Implements variation E.1 from the card-actions exploration (#6835) as an experiment behind card_save_on_hover (default false, evaluated for logged-in users only — same gating as engagement_bar_v2). Control is unchanged.

Treatment — grid cards only (article, share, video, post, poll, collection, X post)

  • The bookmark moves to the card header, right before ⋯ (Read post · bookmark · ⋯). Like Read post and ⋯ it is hidden at rest on desktop and shown on hover; always visible on touch. For keyboard users it appears once focus is already inside the card (the card link or a bar button) and is reachable from there — Tab can't land on it from outside, same as Read post and ⋯ today.
  • The bar is the same bar control renders, only bigger: today's v1 bar (or v2 for engagement_bar_v2 users), same order — upvote · comment · downvote · copy link · impressions — at 32px with 20px icons, in the same 36px row, so card height doesn't change. The arm tests two things only: bookmark → header and 24 → 32px.
  • Saved or not, the header bookmark is hover-only on desktop (product call: the header is empty at rest in every state). On touch it is always visible, filled when saved.
  • List and signal cards are not part of the experiment: they render exactly as control and never evaluate the flag, so they never enroll anyone.
  • Wide featured cards have no header slot, so they keep the bookmark in the bar.

Files

  • featureManagement.ts — featureCardSaveOnHover; hooks/cards/useCardSaveOnHover.ts (logged-in only, plus a shouldEvaluate the bar sets to variant === 'grid').
  • ActionButtons.tsx — treatment grid bars keep control's bar: ActionButtonsV1 gains an internal large prop (32px buttons, 20px icons, py-0.5) and bookmarkInHeader; v2 users get ActionButtonsV2 at compact. ActionButtons.v2.tsx gains density and bookmarkInHeader. No new bar component.
  • CardHeaderBookmark.tsx + a bookmark slot on PostCardHeader, SquadPostCardHeader, CollectionCardHeader and the inline SocialTwitterGrid header; the six grid cards pass it.
  • SocialTwitterGrid.tsx — also fixes four pre-existing strict-mode errors the changed-files guard surfaces.
  • Storybook Experiments/Card save on hover — every grid card type control vs treatment (at rest and hovered) and the list cards, built from the shared test fixtures.

Verified

  • Card height identical control vs treatment on all six grid card types; list cards identical to control; nothing overflows down to 272px.
  • Treatment renders the v1 bar on every grid card type; bar row 36px, buttons 32px; header order Read post · bookmark · ⋯; bookmark hidden at rest, visible on hover and with focus inside the card; a saved post's bookmark is hover-only too.
  • Tests: saveOnHover.spec.tsx (26: bar order, control's bar at 32px, v2 users, flag on/off, list and signal not evaluated, header bookmark (saved or not) on every grid card type + click handler) and useCardSaveOnHover.spec.ts (3: logged-in, anonymous, shouldEvaluate); card and feed suites pass; strict changed-files guard and ESLint clean.

Events

No new tracking events. The header bookmark uses the card's existing onBookmarkClick, so bookmark logging is unchanged.

Experiment

Yes — card_save_on_hover (boolean, default false). Control = false, treatment = true, 50/50, hashed on userId, logged-in users only (anonymous visitors are never enrolled).

No namespace needed: the treatment builds on whichever bar the user already has, so it doesn't conflict with engagement_bar_v2 (which isn't live in production today).

Suggested metrics: post clicks and time on site per active day (goal); bookmarks per user (guardrail — saving is hover-only on desktop).

🤖 Generated with Claude Code

Preview domain

https://feed-card-save-on-hover-dailydot.preview.app.daily.dev

@vercel

vercel Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
daily-webapp Ready Ready Preview Oct 11, 2026 8:09am UTC

Request Review

@tsahimatsliah tsahimatsliah left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reviewed as a draft on request. Control path checked against main: ActionButtonsV1/V2 are untouched, the new bookmark slot renders nothing when undefined, and the flag defaults to false with evaluation gated on a logged-in user, so flag-off behaviour is unchanged. CI is green (typecheck_strict_changed, test_shared, test_webapp, lint). The SocialTwitterGrid strict-mode fixes are scope drift but are what the changed-files guard requires, so they are fine here.

Blocking: the committed storybook snapshot carries real users' identities, the treatment bar is a third copy of the engagement bar, and list/signal cards get a bar that is squeezed into the old row height with negative margins and an invisible hit-area extension rather than being left at control. Details inline. (Posted as a comment review: the account used here is the PR author, so GitHub refuses a formal request-changes.)

Non-blocking: experiment overlap with engagement_bar_v2 needs a decision before start (namespace or mutually exclusive evaluation), and the keyboard-focus claim for the header bookmark is narrower than the description says.

Reviewed by AI.

Comment thread packages/storybook/stories/experiments/cardSaveOnHover.posts.json Outdated
Comment thread packages/shared/src/components/cards/common/ActionButtons.saveOnHover.tsx Outdated
Comment thread packages/shared/src/components/cards/common/ActionButtons.saveOnHover.tsx Outdated
Comment thread packages/shared/src/styles/components/buttons-v2.css Outdated
Comment thread packages/shared/src/components/cards/common/ActionButtons.tsx Outdated
Comment thread packages/shared/src/components/cards/common/CardHeaderBookmark.tsx
…der, bigger bar

Behind `card_save_on_hover` (default off, logged-in users only).

Treatment, grid cards only (article, share, video, post, poll,
collection, X post):
- The bookmark moves to the card header, right before ⋯. Like Read post
  and ⋯ it is hidden at rest on desktop and shown on hover; it is always
  visible on touch.
- The bar is the existing v2 bar at `compact` density: today's order
  (upvote, comment, downvote, copy link, impressions) at 32px with 20px
  icons, in the same 36px row, so cards keep their height.
- `ActionButtonsV2` gains `density` and `bookmarkInHeader`; no new bar.

List and signal cards are not part of the experiment and stay at
control. Wide featured cards have no header slot and keep the bookmark
in the bar. Run the experiment in a GrowthBook namespace exclusive with
`engagement_bar_v2`.

Also fixes four pre-existing strict-mode errors in SocialTwitterGrid
that the changed-files guard surfaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tsahimatsliah

Copy link
Copy Markdown
Member Author

Review follow-up — all 6 threads addressed and resolved

Blocking

  • Real users in the story fixture → JSON removed (commit amended, not in history); the story uses the shared test fixtures.
  • Third copy of the bar → ActionButtons.saveOnHover.tsx deleted; ActionButtonsV2 gained density + bookmarkInHeader, and treatment grid cards render <ActionButtonsV2 density="compact" bookmarkInHeader />. 1,084 → 640 added lines.
  • List/signal squeezed into the old row → list and signal cards are now exactly control; -my-1 and the reach class are gone.

Non-blocking

  • 44px reach pseudo-element → removed; buttons-v2.css is back to main. 32px buttons clear WCAG 2.5.8 on their own.
  • Overlap with engagement_bar_v2 → a mutually exclusive GrowthBook namespace is now a stated requirement for starting the experiment (description + comment in ActionButtons.tsx).
  • Keyboard wording → doc comment and description now say the bookmark appears once focus is inside the card; Tab can't land on it from outside.
  • Also: CardHeaderBookmark imports useFeedPreviewMode from its source file, not the hooks barrel (AGENTS.md).

Re-verified: card heights identical to control on all grid card types, list cards identical to control, 18/18 in saveOnHover.spec.tsx, card + feed suites green, strict guard + ESLint clean.

…header

List cards now match grid cards: the bookmark leaves the bottom bar and
sits in the header right before ⋯ (hover-only on desktop, always visible
on touch). The list bar keeps today's 24px buttons, so row heights are
unchanged. Signal cards have no header ⋯ and keep the bar bookmark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… to the header"

List cards go back to control: the bookmark stays in the bottom bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@idoshamun idoshamun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-review of the current head (a925954). The earlier threads are addressed: one bar, list/signal back at control, no real-user fixture. CI is green. One blocking issue on enrollment and one experiment-design question, both inline, plus a product question about bookmarked state. Both of the first two have to be settled before the experiment starts, because fixing them later means re-keying the flag and throwing away the enrolments collected so far.

The PR is still marked as a draft.

Reviewed by AI.

Comment thread packages/shared/src/components/cards/common/ActionButtons.tsx Outdated
Comment thread packages/shared/src/components/cards/common/ActionButtons.tsx
Comment thread packages/shared/src/components/cards/common/CardHeaderBookmark.tsx
@tsahimatsliah
tsahimatsliah marked this pull request as ready for review October 9, 2026 07:42
tsahimatsliah and others added 2 commits October 9, 2026 10:48
…ol's bar

- Only grid bars evaluate the flag (`useCardSaveOnHover({ shouldEvaluate })`),
  so list and signal renders never enroll users who can't see a change.
- The treatment keeps the bar control renders (v1, or v2 for
  engagement_bar_v2 users) at 32px instead of swapping in the v2 bar, so the
  test measures the bookmark move and the size only. No namespace needed.
- A saved post keeps its filled bookmark visible at rest in the header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dev' into feed-card-save-on-hover-dailydotdev
@tsahimatsliah

Copy link
Copy Markdown
Member Author

Re-review follow-up: all 3 threads fixed, replied to and resolved (c45afd7)

  • Enrollment (blocking): only grid bars evaluate card_save_on_hover (useCardSaveOnHover({ shouldEvaluate: variant === 'grid' })). List and signal renders never enroll anyone. Covered by tests.
  • Bar confound (question): engagement_bar_v2 isn't live in production, so the treatment now keeps control's v1 bar at 32px instead of swapping in v2. The arm tests the bookmark move and the size only, and the namespace requirement is dropped.
  • Saved state (product): a bookmarked post keeps its filled bookmark visible in the header at rest. There's a new "Saved article" row in the story.

Card height is unchanged on every grid card type, nothing overflows at 272px, and the card/feed suites pass (398 tests). The description is updated.

…saved

Product call: the header is empty at rest on desktop whether or not the
post is saved, so the saved-state exception is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@idoshamun idoshamun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed at 4f59b47. The enrollment, bar-swap and saved-state threads are all resolved: only grid renders evaluate card_save_on_hover, the treatment keeps control's bar and changes only its size and where the bookmark sits, and hover-only saved state is a documented product call. CI is green.

Reviewed by AI.

Approved through AI review.

@tsahimatsliah
tsahimatsliah merged commit c418a70 into main Oct 11, 2026
11 checks passed
@tsahimatsliah
tsahimatsliah deleted the feed-card-save-on-hover-dailydotdev branch October 11, 2026 08:12

This branch was successfully deployed

1 active deployment
Preview — 1e67e797 Deployed Oct 11, 2026 by vercel[bot]
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.

2 participants