Skip to content

Feat: extend hover-mirror cursor sync to all consumer view question types#4834

Open
ncarazon wants to merge 2 commits into
mainfrom
feat/consumer-view-hover-sync-all-question-types
Open

Feat: extend hover-mirror cursor sync to all consumer view question types#4834
ncarazon wants to merge 2 commits into
mainfrom
feat/consumer-view-hover-sync-all-question-types

Conversation

@ncarazon
Copy link
Copy Markdown
Contributor

@ncarazon ncarazon commented Jun 4, 2026

Resolves #4764

Summary

In consumer view, hovering the forecast timeline only synced the mini-distribution on the left for continuous questions. All other question types showed stale values regardless of cursor position. Tooltip also duplicated information already visible in the sidebar.

Implemented changes:

  • ContinuousChartCursorContext: added activeBinaryValue / setActiveBinaryValue to carry the binary CP value at cursor position
  • DetailedContinuousChartCard: computes CP probability at cursor timestamp and pushes it to context via useLayoutEffect for same-frame gauge updates; suppresses floating tooltip in consumer binary view
  • ConsumerListChartShell: added cursorTimestamp / setCursorTimestamp to ListChartExpandedContext so sidebar components can read the hovered timestamp; resets to null on mouse leave
  • DetailedMultipleChoiceChartCard: pushes cursor timestamp to context in consumer view; hides floating tooltip
  • GroupTimeline: added onCursorChange and hideTooltip props and wires them through to MultiChoicesChartView
  • ConsumerGroupChart: forwards cursor timestamp to context and hides tooltip for continuous groups
  • PercentageForecastCard: resolves each choice's display value at the cursor timestamp using per-choice history lookup (fixes wrong initial values when sub-questions have different history lengths)
  • NumericForecastCard: same cursor-aware value resolution for numeric group sub-questions
  • QuestionHeaderCPStatus / BinaryCPBar: accept cursorBinaryValue / overrideValue to display cursor-position CP instead of latest
  • QuestionPageShell: extracted BinaryCursorGauge as an isolated context consumer so ConsumerShell no longer re-renders on every cursor move
  • DropdownMenu: converted prevItems from state to ref to prevent a render loop triggered by the increased re-render frequency

Demo video

feat-consumer-view-hover-sync-all-question-types.mp4

Summary by CodeRabbit

  • New Features

    • Chart cursor now synchronizes displayed forecast values in real-time across choice predictions and gauges
    • Binary forecast gauge dynamically updates with chart cursor position
  • Improvements

    • Enhanced tooltip behavior in chart views for better clarity
    • Improved chart cursor state coordination between multiple components
  • Bug Fixes

    • Fixed dropdown menu state restoration when menu closes

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 78154647-36ec-43aa-906e-15d9c8198b86

📥 Commits

Reviewing files that changed from the base of the PR and between 99186a6 and 2ab690d.

📒 Files selected for processing (2)
  • front_end/src/components/consumer_post_card/group_forecast_card/numeric_forecast_card.tsx
  • front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx

📝 Walkthrough

Walkthrough

This PR extends chart cursor synchronization across question types, enabling hovering on forecast timelines to update corresponding sidebar displays in consumer views while controlling tooltip visibility. Changes span context state management, component prop wiring, and aggregation-value timestamp alignment.

Changes

Chart Cursor Synchronization and Consumer-View Mirror Updates

Layer / File(s) Summary
Context state extension for cursor tracking
front_end/src/contexts/continuous_chart_cursor_context.tsx, front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_list_chart_shell.tsx
ContinuousChartCursorProvider now tracks activeBinaryValue for binary question cursor position. ListChartExpandedContext adds cursorTimestamp state and exposes it via useListChartExpanded hook, enabling shared cursor timestamp across consumer-view sidebar components.
Tooltip and cursor prop wiring through chart components
front_end/src/app/(main)/questions/[id]/components/group_timeline.tsx, front_end/src/app/(main)/questions/[id]/components/multiple_choices_chart_view/index.tsx, front_end/src/components/detailed_question_card/detailed_group_card/index.tsx
GroupTimeline and MultiChoicesChartView accept optional onCursorChange callback and hideTooltip flag. DetailedGroupCard forwards both props through to GroupTimeline. Tooltip rendering is gated by the hideTooltip flag.
Consumer view cursor synchronization to sidebar
front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_group_chart.tsx, front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/timeline/index.tsx, front_end/src/components/detailed_question_card/detailed_question_card/multiple_choice_chart_card.tsx
Chart cursor changes are forwarded to ListChartExpandedContext via setCursorTimestamp callback when rendering in consumer view. ConsumerGroupChart, QuestionTimeline, and MultipleChoiceChartCard handle this wiring conditionally based on isConsumerView flag.
Binary cursor gauge and CP header integration
front_end/src/components/detailed_question_card/detailed_question_card/continuous_chart_card.tsx, front_end/src/app/(main)/questions/[id]/components/question_page_shell/index.tsx, front_end/src/app/(main)/questions/[id]/components/question_view/forecaster_question_view/question_header/question_header_cp_status.tsx, front_end/src/components/consumer_post_card/binary_cp_bar.tsx
ContinuousChartCard derives activeBinaryValue from cursor position and pushes it to context. question_page_shell wraps QuestionHeaderCPStatus in a BinaryCursorGauge component that reads the cursor value from context and passes it as cursorBinaryValue override to the header. BinaryCPBar uses this override value instead of the default aggregation center.
Numeric and percentage card timestamp alignment
front_end/src/components/consumer_post_card/group_forecast_card/numeric_forecast_card.tsx, front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx
NumericForecastCard and PercentageForecastCard now derive displayed values and percentages from the cursor-resolved timestamp instead of always using the latest aggregation. Both import and use findPreviousTimestamp to map cursorTimestamp to each choice's own aggregation timeline, enabling synchronized value display across sidebar and chart.
Dropdown menu state management refactor
front_end/src/components/ui/dropdown_menu.tsx
Component now uses useRef to track the previous items prop instead of React state, simplifying cleanup logic and eliminating a state dependency.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • elisescu
  • hlbmtc
  • cemreinanc

🐰 Hovering charts now ripple through sidebar bars,
Binary gauges dance with cursor stars,
Timestamps align from sea to sky,
Synchronize thy forecasts high!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: extending cursor sync to all consumer view question types. It is specific and clearly conveys the primary change without being overly verbose.
Linked Issues check ✅ Passed The PR fully implements the objective from issue #4764 to extend hover-mirror behavior to groups, multiple choice, and binary questions across consumer view.
Out of Scope Changes check ✅ Passed The DropdownMenu ref conversion is a necessary fix to prevent render loops caused by the increased re-render frequency from cursor changes, making it in-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/consumer-view-hover-sync-all-question-types

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
front_end/src/components/ui/dropdown_menu.tsx (1)

67-82: ⚡ Quick win

Ref-based prevItems refactor correctly prevents render loop.

The change from useState to useRef for tracking the base menu items is correct and successfully addresses the render loop issue described in the PR objectives. By eliminating prevItems as state, updates to the ref no longer trigger re-renders, breaking the cascade of re-renders caused by increased cursor synchronization activity elsewhere in the codebase.

The restoration logic remains functionally correct: prevItemsRef.current always holds the latest items prop (refs don't suffer from stale closures), so resetting activeItems on menu close works as intended.

Further optimize the effect by stabilizing onClose access:

The second effect currently depends on [open, onClose]. If the parent doesn't memoize onClose, the effect will run every time the parent re-renders while the menu is closed, invoking onClose() spuriously. Consider capturing the latest onClose via a ref and depending only on [open]:

♻️ Suggested effect pattern to eliminate onClose dependency
  const prevItemsRef = useRef<MenuItemProps[]>(items);
+ const onCloseRef = useRef(onClose);
  const [activeItems, setActiveItems] = useState<MenuItemProps[]>(items);

+ useEffect(() => {
+   onCloseRef.current = onClose;
+ }, [onClose]);
+
  // Track prop change
  useEffect(() => {
    prevItemsRef.current = items;
    setActiveItems(items);
  }, [items]);

  useEffect(() => {
    if (!open) {
      setActiveItems(prevItemsRef.current);
-     if (onClose) onClose();
+     if (onCloseRef.current) onCloseRef.current();
    }
- }, [open, onClose]);
+ }, [open]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@front_end/src/components/ui/dropdown_menu.tsx` around lines 67 - 82, The
effect that resets activeItems on close currently lists onClose in its deps
causing spurious runs when parents re-render; create a ref (e.g., onCloseRef) to
store the latest onClose and update it in an effect, then change the
close-effect to depend only on [open] and call onCloseRef.current() (guarded)
instead of onClose; update references to use onCloseRef so the effect no longer
re-triggers unnecessarily while preserving correct latest-callback behavior
(refer to prevItemsRef, activeItems, setActiveItems, and the close-useEffect).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@front_end/src/components/consumer_post_card/group_forecast_card/numeric_forecast_card.tsx`:
- Around line 81-87: The fallback refTs is currently taken from sortedChoices[0]
which can point to an older series due to earlier reordering; update the logic
that computes refTimestamps/refTs to use a global latest timestamp instead of
the first sorted row — for example derive refTimestamps from the last element
(sortedChoices[sortedChoices.length - 1]?.aggregationTimestamps) or compute the
max last-entry timestamp across all sortedChoices, then keep the existing
cursorTimestamp override logic (use cursorTimestamp if not null, otherwise use
that global latest timestamp or null). Ensure you update references to
refTimestamps and refTs accordingly.

In
`@front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx`:
- Around line 94-98: The fallback reference timestamp is currently taken from
allChoices[0] (refTimestamps) which can be shorter than other choices; change
the fallback logic so that when cursorTimestamp is null you compute the latest
available timestamp across all choices by inspecting each choice. Specifically,
replace using allChoices[0]?.aggregationTimestamps and
refTimestamps[refTimestamps.length - 1] with logic that iterates over
allChoices, extracts each choice.aggregationTimestamps' last element (if any),
finds the maximum (most recent) timestamp, and use that as the fallback refTs;
preserve the early returns when there are no timestamps or refTs is null.

---

Nitpick comments:
In `@front_end/src/components/ui/dropdown_menu.tsx`:
- Around line 67-82: The effect that resets activeItems on close currently lists
onClose in its deps causing spurious runs when parents re-render; create a ref
(e.g., onCloseRef) to store the latest onClose and update it in an effect, then
change the close-effect to depend only on [open] and call onCloseRef.current()
(guarded) instead of onClose; update references to use onCloseRef so the effect
no longer re-triggers unnecessarily while preserving correct latest-callback
behavior (refer to prevItemsRef, activeItems, setActiveItems, and the
close-useEffect).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0dc2f2f2-bf08-4ae8-8535-533daa056aad

📥 Commits

Reviewing files that changed from the base of the PR and between 7e6717d and 99186a6.

📒 Files selected for processing (15)
  • front_end/src/app/(main)/questions/[id]/components/group_timeline.tsx
  • front_end/src/app/(main)/questions/[id]/components/multiple_choices_chart_view/index.tsx
  • front_end/src/app/(main)/questions/[id]/components/question_page_shell/index.tsx
  • front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_group_chart.tsx
  • front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_list_chart_shell.tsx
  • front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/timeline/index.tsx
  • front_end/src/app/(main)/questions/[id]/components/question_view/forecaster_question_view/question_header/question_header_cp_status.tsx
  • front_end/src/components/consumer_post_card/binary_cp_bar.tsx
  • front_end/src/components/consumer_post_card/group_forecast_card/numeric_forecast_card.tsx
  • front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx
  • front_end/src/components/detailed_question_card/detailed_group_card/index.tsx
  • front_end/src/components/detailed_question_card/detailed_question_card/continuous_chart_card.tsx
  • front_end/src/components/detailed_question_card/detailed_question_card/multiple_choice_chart_card.tsx
  • front_end/src/components/ui/dropdown_menu.tsx
  • front_end/src/contexts/continuous_chart_cursor_context.tsx

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 4, 2026

🚀 Preview Environment

Your preview environment is ready!

Resource Details
🌐 Preview URL https://metaculus-pr-4834-feat-consumer-view-hover-sync-preview.mtcl.cc
📦 Docker Image ghcr.io/metaculus/metaculus:feat-consumer-view-hover-sync-all-question-types-2ab690d
🗄️ PostgreSQL NeonDB branch preview/pr-4834-feat-consumer-view-hover-sync
Redis Fly Redis mtc-redis-pr-4834-feat-consumer-view-hover-sync

Details

  • Commit: 84e2087680ae1e433cff9386cf30712c165c9cd7
  • Branch: feat/consumer-view-hover-sync-all-question-types
  • Fly App: metaculus-pr-4834-feat-consumer-view-hover-sync

ℹ️ Preview Environment Info

Isolation:

  • PostgreSQL and Redis are fully isolated from production
  • Each PR gets its own database branch and Redis instance
  • Changes pushed to this PR will trigger a new deployment

Limitations:

  • Background workers and cron jobs are not deployed in preview environments
  • If you need to test background jobs, use Heroku staging environments

Cleanup:

  • This preview will be automatically destroyed when the PR is closed

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.

Extend hover:mirror behavior of charts to other question types

1 participant