Skip to content

In feed agents highlight#5797

Closed
tomeredlich wants to merge 19 commits intomainfrom
in-feed-agents-highlight
Closed

In feed agents highlight#5797
tomeredlich wants to merge 19 commits intomainfrom
in-feed-agents-highlight

Conversation

@tomeredlich
Copy link
Copy Markdown
Contributor

Changes

Events

Did you introduce any new tracking events?

Experiment

Did you introduce any new experiments?

Manual Testing

Caution

Please make sure existing components are not breaking/affected by this PR

Add a dark-style highlights module to My Feed top content using post highlights data, with inline row interactions and an agents footer link. Gate rollout behind a feature flag and log impression/click analytics for engagement tracking.

Made-with: Cursor
Apply formatting and accessibility-safe link updates for the new feed highlights top module and its feed wiring so changed-file lint checks pass cleanly.

Made-with: Cursor
Refresh the in-feed agents highlights module with a more contemporary dark panel style, stronger hierarchy, and improved row affordance while preserving inline interaction behavior and agents footer link.
EOF && git status --short

Made-with: Cursor
Move highlights to the first feed card slot and align header controls with existing card hover-actions, including subscribe and hide options. Polish visual density, footer placement, and staggered notification-style bubble entrance animation.

Made-with: Cursor
Apply the previewed feed highlights updates so the module layout, CTA behavior, and gradient styling match the intended in-product design refinements.

Made-with: Cursor
Deliver the new floating/minimized highlights experience with dismiss/minimize behavior, unread/new-item signaling, and refreshed styling so live feed updates are noticeable without being intrusive.

Made-with: Cursor
…ling

Improve the highlights module UX by restoring hover actions in the header menu, gating subscribe based on real follow state, and aligning list-layout visuals with feed patterns. This also adds a brief skeleton phase to signal fresh updates while keeping UI2 disabled.

Made-with: Cursor
Remove feed highlights load-entry/skeleton animations, tighten card spacing, and add explicit now/viewed/pressed/read states so item status is clear during a session.

Made-with: Cursor
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 25, 2026

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

Project Deployment Actions Updated (UTC)
daily-webapp Error Error Apr 2, 2026 5:52pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
storybook Ignored Ignored Apr 2, 2026 5:52pm

Request Review

@tomeredlich
Copy link
Copy Markdown
Contributor Author

@claude check pr

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 25, 2026

Claude finished @tomeredlich's task in 2m 18s —— View job


PR Review: In feed agents highlight

  • Gathering context
  • Reading changed files
  • Analyzing code
  • Posting review

Summary

This PR adds a "Happening Now" highlights module to the top of My Feed, backed by a POST_HIGHLIGHTS_QUERY GraphQL query and gated behind a featureFeedHighlightsModule feature flag (+ development override). The component also ships scaffolding for a "v2" floating widget/minimizable popover that is never activated.


Issues

🔴 Dead code: isFeedHighlightsUi2Enabled = false

FeedHighlightsTopModule.tsx:40 has this hardcoded:

const isFeedHighlightsUi2Enabled = false;

This constant gates a very large block of code that never executes: the entire floating popover section (~90 lines), the collapsed button section (~30 lines), unreadCount state, isPopoverOpen state, isDismissed state, the scroll listener effect, the simulate event listener, the minimize cooldown localStorage logic, and three useEffect hooks. Per the CLAUDE.md "Delete dead code" guideline, this should be removed if the feature isn't being shipped in this PR, or activated and tested if it is.

Fix this →

🔴 Dead CSS: .feed-highlights-menu-content never applied

utilities.css:678-686 defines:

.feed-highlights-menu-content {
  min-width: 12rem;
  ...
}
.feed-highlights-menu-content .DropdownMenuItem {
  height: 1.5rem;
  ...
}

But className="feed-highlights-menu-content" is never applied to <DropdownMenuContent> in FeedHighlightsTopModule.tsx (line 396). These styles have no effect. Either apply the class or remove the CSS.

Similarly, the notification-bubble-enter keyframe animation added to utilities.css:112-123 is never referenced anywhere in the component.

Fix this →


Observations

🟡 highlightAsNew is always index === 0

FeedHighlightsTopModule.tsx:413:

highlightAsNew={index === 0}

The first highlight always shows "Now" as the timestamp regardless of its actual age. This is probably intentional UX but worth confirming — if a highlight is 3 hours old it would still show "Now".

🟡 markHighlightViewed / markHighlightRead defined inside render

These functions at lines 354-375 are recreated on every render. They don't need to be defined inside the render body since they only call setState. They could be useCallback hooks or at minimum defined outside the if check block. Minor since HighlightRow isn't memoized, but inconsistent with the pattern used for callbacks in Feed.tsx.

🟡 No staleTime on the highlights query

Feed.tsx:344-351 — TanStack Query defaults to staleTime: 0, meaning the highlights will refetch every time the window regains focus. For a "Happening Now" module this may be intentional (always fresh data), but it's worth explicitly documenting or setting a staleTime if refetching on focus isn't desired.

🟡 digestSource as Source unsafe cast

FeedHighlightsTopModule.tsx:145:

const { isFollowing, toggleFollow } = useSourceActions({
  source: digestSource as Source,
});

digestSource may be undefined (query is loading or errored), and the cast bypasses TypeScript's null safety. Confirm useSourceActions handles an undefined source gracefully.

🟢 Good patterns

  • Feature flag gated with shouldEvaluate: isMyFeed to avoid unnecessary GrowthBook evaluations ✓
  • isDevelopment override for local dev testing ✓
  • Impression tracking with ref guard to avoid double-firing ✓
  • prefers-reduced-motion respected for gradient animations ✓
  • type="button" set on all buttons ✓
  • Proper RequestKey.PostHighlights used for query key ✓

Refresh highlights on mount/focus/reconnect with periodic polling, and preserve the updated Happening Now module and modal interaction flow.

Made-with: Cursor
Move the Happening Now title into the modal nav and split highlights into a dedicated horizontal strip with explicit scroll controls so the modal stays stable while navigating posts.

Made-with: Cursor
Scope the happening now modal to compact card-style options, synchronized highlight navigation, and modal-aligned controls while refining title animation and menu sizing.

Made-with: Cursor
Introduce a mobile bottom drawer for happening now highlights with minimize/expand behavior and swipe-driven article navigation while keeping desktop behavior unchanged.

Made-with: Cursor
Switch the feed highlights CTA to open the Happening Now modal directly on the first story and align mobile/desktop behavior so reading continues in-context. This also fixes modal content overlap under the floating highlights strip and keeps drawer spacing consistent.

Made-with: Cursor
Preserve the original card as v1 and implement a v2 variant with source metadata styling updates, three-item limit, and simplified row dividers for quick UI iteration in feed highlights.

Made-with: Cursor
Remove extra vertical gaps in the highlights list container and item rows so the module matches the updated compact layout from preview.

Made-with: Cursor
Align happening now highlight controls and interactions with the updated feed card behavior by using primary floating nav buttons, removing hover/press-only row styling, and restoring the selected item only after user idle.

Made-with: Cursor
@tomeredlich
Copy link
Copy Markdown
Contributor Author

@claude check pr

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 2, 2026

Claude finished @tomeredlich's task in 2m 12s —— View job


PR Review: In feed agents highlight

  • Gathering context
  • Reading changed files
  • Analyzing code
  • Posting review

Summary

This PR ships the "Happening Now" highlights module: a FeedHighlightsTopModule card at the top of My Feed backed by POST_HIGHLIGHTS_QUERY, plus a full HappeningNowPostModal with swipe navigation, keyboard shortcuts, and desktop/mobile layouts. The previous review's dead code (isFeedHighlightsUi2Enabled) has been cleaned up in commit b32872c. Here are the remaining issues:


Issues

🔴 AGENTS_DIGEST_SOURCE_ID duplicated in two files

The constant is defined identically in both FeedHighlightsTopModule.tsx:36 and HappeningNowPostModal.tsx (inside the component). Per the CLAUDE.md code duplication guideline, this should be defined once and imported.

Fix this →

🔴 HighlightRowV1 is dead code

FEED_HIGHLIGHTS_CARD_VERSION = 'v2' as const means HighlightRowComponent is always HighlightRowV2. HighlightRowV1 is never rendered. Per CLAUDE.md: "Delete dead code".

Similarly, the highlightAsNew prop is passed to HighlightRowV2 (FeedHighlightsTopModule.tsx:413) but is never used inside the HighlightRowV2 component body — it's destructured but ignored.

Fix this →

🔴 SWIPE_COMMIT_DURATION_MS and DESKTOP_HIGHLIGHTS_RESTORE_IDLE_MS defined inside component, referenced in useCallback deps

HappeningNowPostModal.tsx:

const SWIPE_COMMIT_DURATION_MS = 220;
const DESKTOP_HIGHLIGHTS_RESTORE_IDLE_MS = 900;

These primitives are redeclared on every render, and then appear in useCallback dependency arrays (commitSwipeNavigation, scheduleDesktopHighlightsRestore). While React will still compare them by value (numbers are primitives), including them in deps is unnecessary noise and misleading. Move them to module-level constants.

Fix this →

🔴 source field added to GraphQL query and type but never used

highlights.ts adds source { name image } to both the TypeScript interface and the GQL query, but neither FeedHighlightsTopModule nor HappeningNowPostModal renders anything from highlight.post.source. This adds an unnecessary field to every network request.

Either use it or remove it.

Fix this →


Observations

🟡 Duplicate highlight tab list JSX in HappeningNowPostModal

The highlight tab buttons are copy-pasted almost identically for desktop (~lines 637–677) and mobile (~lines 681–727) layouts — same data-highlight-index, same key, same className logic, same RelativeTime + headline structure. The only difference is gap-1 pr-10 vs gap-1 pr-2 and the desktop scroll arrow buttons. Consider extracting a HighlightTabList sub-component to avoid the duplication.

🟡 digestSource as Source unsafe cast in both files

Both FeedHighlightsTopModule.tsx:145 and HappeningNowPostModal.tsx pass digestSource as Source to useSourceActions. digestSource can be undefined while the query is loading. Confirm useSourceActions handles undefined gracefully, or guard with digestSource ?? undefined (already undefined, but remove the cast).

🟡 onHappeningNowPostModalClose defined inside render body in Feed.tsx

Feed.tsx:~635:

const onHappeningNowPostModalClose = (): void => {
  setSelectedHighlightPostId(null);
};

This is defined in the render body without useCallback. Since it's passed as a prop to a dynamically loaded modal, it will cause unnecessary reference changes on every render. Wrap in useCallback or use an inline () => setSelectedHighlightPostId(null) consistently with the existing pattern.

🟡 feed-highlights-new-item-border CSS class is defined but never applied

utilities.css defines .feed-highlights-new-item-border (the full border animation class), but the JSX only uses .feed-highlights-new-item-border-bottom. The full border class is dead CSS.


What's good

  • Dead code from previous review (isFeedHighlightsUi2Enabled) cleaned up ✓
  • Feature flag properly gated with shouldEvaluate: isMyFeed
  • isDevelopment override for local testing ✓
  • ssr: false on dynamic HappeningNowPostModal import ✓
  • Impression logging with ref guard (no double-fire) ✓
  • prefers-reduced-motion respected in CSS ✓
  • type="button" on all buttons ✓
  • Keyboard arrow navigation in modal ✓
  • Swipe gesture navigation with transition animation ✓
  • Auto-scroll active highlight into view with retry loop ✓
  • refetchInterval for keeping highlights fresh ✓

Clean up the highlights module by removing dead v1 row paths, sharing the agents digest source id across components, moving modal timing constants to module scope, and dropping unused source fields from the highlights query payload.

Made-with: Cursor
@tomeredlich
Copy link
Copy Markdown
Contributor Author

@claude check pr

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 2, 2026

Claude finished @tomeredlich's task in 1m 47s —— View job


PR Review: In feed agents highlight

  • Gathering context
  • Reading changed files
  • Analyzing code
  • Posting review

Summary

The previous two rounds of feedback have been well addressed. Most red issues are resolved: AGENTS_DIGEST_SOURCE_ID is now exported from highlights.ts and imported in both files, HighlightRowV1 and FEED_HIGHLIGHTS_CARD_VERSION are deleted, the source field was removed from the GQL query, and SWIPE_COMMIT_DURATION_MS / DESKTOP_HIGHLIGHTS_RESTORE_IDLE_MS are now module-level constants. Here's what remains:


Issues

🔴 Dead CSS: .feed-highlights-new-item-border never applied

utilities.css:683–699 defines .feed-highlights-new-item-border (the full animated border), but every call site in HappeningNowPostModal.tsx uses only .feed-highlights-new-item-border-bottom. The full-border class has zero effect and should be removed.

Fix this →

🟡 onHappeningNowPostModalClose defined without useCallback in render body

Feed.tsx:638–640:

const onHappeningNowPostModalClose = (): void => {
  setSelectedHighlightPostId(null);
};

This plain function reference is recreated every render and passed to the dynamically-loaded HappeningNowPostModal. Wrap it with useCallback alongside the other callbacks in the component, or use an inline () => setSelectedHighlightPostId(null) consistently. The companion onPostModalClose at line 633 has the same pattern, so either refactor both or leave as-is for consistency — just flag the asymmetry.

Fix this →

🟡 markHighlightViewed / markHighlightRead defined in render body without useCallback

FeedHighlightsTopModule.tsx:188–209 — these two functions are recreated on every render. Each HighlightRowV2 receives fresh onViewed/onRead props on every render. Consider useCallback or moving them above the early-return block where they can be memoized.

🟡 Duplicate highlight tab list JSX

HappeningNowPostModal.tsx:672–700 (desktop) and :727–756 (mobile) render nearly identical button elements — same data-highlight-index, same key, same class logic, same RelativeTime + headline structure. The only difference is pr-10 vs pr-2 on the wrapper. Extracting a shared sub-component would eliminate ~27 lines of duplication.

🟡 digestSource as Source unsafe cast

Both FeedHighlightsTopModule.tsx:126 and HappeningNowPostModal.tsx:102 pass digestSource as Source to useSourceActions. digestSource is undefined while the query is loading. Verify that useSourceActions handles undefined source gracefully, or guard the hook call.


What's good since last review ✓

  • AGENTS_DIGEST_SOURCE_ID deduplicated into highlights.ts and imported ✓
  • HighlightRowV1 + FEED_HIGHLIGHTS_CARD_VERSION dead code removed ✓
  • source { name image } removed from GQL query and type ✓
  • SWIPE_COMMIT_DURATION_MS / DESKTOP_HIGHLIGHTS_RESTORE_IDLE_MS moved to module level ✓
  • isFeedHighlightsUi2Enabled dead code cleaned up ✓

Remove dead highlights border CSS, stabilize modal close callbacks, memoize highlight interaction handlers, extract shared modal highlight tabs markup, and make source action hooks safe for undefined digest sources.

Made-with: Cursor
@tomeredlich
Copy link
Copy Markdown
Contributor Author

@claude check once more

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 2, 2026

Claude finished @tomeredlich's task in 1m 53s —— View job


PR Review: In feed agents highlight

  • Gathering context
  • Reading changed files
  • Analyzing code
  • Posting review

Summary

All previous red issues are now resolved. One new React bug was introduced while fixing the useCallback feedback — the hooks are now placed after conditional early returns, which violates Rules of Hooks.


Issues

🔴 Rules of Hooks violation: useCallback after conditional returns

FeedHighlightsTopModule.tsx:166–208 — The two early returns were already in place before the useCallback hooks were added:

if (isHiddenLocally) {
  return null;                // line 167
}

if (!loading && highlights.length === 0) {
  return null;                // line 171
}

const topHighlights = ...    // line 174

const markHighlightViewed = useCallback(...);  // line 187  ← hook after return ❌
const markHighlightRead = useCallback(...);    // line 198  ← hook after return ❌

React requires all hooks to be called unconditionally, before any early returns. This will produce a "Rendered fewer hooks than expected" error when the component switches between returning null and rendering. Move both useCallback definitions above the early returns.

Fix this →


What's been fixed since last review ✓

  • .feed-highlights-new-item-border dead CSS removed ✓
  • onHappeningNowPostModalClose wrapped with useCallback
  • markHighlightViewed / markHighlightRead wrapped with useCallback (placement is the problem, not the wrapping) ✓
  • Duplicate highlight tab list extracted to HighlightTabs sub-component ✓
  • digestSource as Source unsafe cast removed — hook now accepts Source | null | undefined

@idoshamun idoshamun closed this Apr 5, 2026
@idoshamun idoshamun deleted the in-feed-agents-highlight branch April 5, 2026 18:44
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