Question Page Redesign - Multiple choice + binary group + continuous group + time series#4734
Conversation
…ow question types (#4712) * feat: add ConsumerListChartShell for N-row consumer side-by-side layout * fix: remove extra top/bottom margin on group prediction inside the side-by-side shell and fix styles in ConsumerListChartShell * feat: restyle consumer MC and group forecast rows with chevron expand/collapse and expanded panel * feat: restyle sidebar expand button with ellipsis/border variant and hide legend in consumer MC and binary-group chart views * fix: restore isBordered prop in ForecastChoiceBar and show TimeSeriesChart in fan graph left panel * fix: stretch group forecast card to full width and use minimal expand button in feed * feat: add consumer side-by-side shell for continuous numeric group questions with proportional bars, hover-synced chart highlight, and endpoint dots * fix: scope bar hover effects to wired handlers, fill height for all bar-row types, cover discrete groups, and prevent expand height jump * fix: render minimal expand indicator as div to fix accessibility violation * fix: remove expand button from DOM and clamp hiddenCount when overlay is open
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Cleanup: Preview Environment RemovedThe preview environment for this PR has been destroyed.
Cleanup triggered by PR close at 2026-05-20T12:02:02Z |
* feat: add compact legend bar above MC and binary group chart with per-option checkbox, current %, and resolved-no strikethrough * feat: responsive compact legend bar for MC and binary group charts with mobile item cap, resolved-no collapsing, tooltip suppression, and hidden zoom buttons on mobile * fix: use MC resolved-no detection in binary timeline view * fix: remove pointer/hover affordances from resolved-NO legend items and add aria-label to checkboxes
* feat: add consumer Fan/Timeline toggle with side-by-side bar chart, hover sync, and color-aligned time series visualisation for FanGraph * fix: enable mobile fan chart tooltip in consumer view due to async timing issue that kept the tooltip invisible * fix: support touch drag tracking on fan chart tooltip and simplify tooltip rendering * fix: fan chart toggle order and default differ between consumer and forecaster views * fix: mobile fan chart tooltip: matches desktop hover behavior, dismisses outside tap, no page shift on drag * fix: group timeline touch tooltip now works on first tap, follows finger drag, and shows endpoint dots on all lines * fix: duplicate fan chart tooltip on desktop hover * fix: handle touchcancel, sticky tooltip/cursor state, and bar highlight edge cases across charts
* feat: date group consumer visual restyle with iter 3 side-by-side layout (bars left, scatter chart right) * fix: scope date group visual changes to question page only; border-only bars in mobile timeline tab * refactor: use cn() for className in DateForecastCard
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
front_end/src/components/charts/group_chart.tsx (1)
383-387:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winExclude the left gutter from
inPlotchecks.These bounds start at
0, so any non-zeroleftPaddingmakes the y-axis gutter count as plot space. That activates hover/touch sync and clamps the cursor to the first timestamp even when the pointer is outside the drawable area.Proposed fix
- const inPlot = - x >= 0 && + const inPlot = + x >= leftPadding && x <= chartWidth - maxRightPadding && y >= PLOT_TOP && y <= plotBottom;Apply the same bound in the mouse and both touch handlers.
Also applies to: 415-419, 445-449
🤖 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/charts/group_chart.tsx` around lines 383 - 387, The inPlot check currently uses x >= 0 which wrongly includes the left gutter; change the horizontal lower bound to the left gutter variable (use leftPadding or the existing left gutter constant) so the condition becomes x >= leftPadding (and keep x <= chartWidth - maxRightPadding), and apply this same fix in the mouse handler and both touch handlers where inPlot is computed so hover/touch sync and cursor clamping only activate inside the actual drawable plot area.front_end/src/components/consumer_post_card/group_forecast_card/forecast_choice_bar.tsx (1)
115-159:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winTreat
0as a resolved value in the overlay styling.A valid numeric/date resolution of
0falls through these truthy checks, so the row can render with unresolved fill/border styling even though it is resolved. Please keep these checks consistent with the!isNil(resolution)guard already used above.Suggested fix
<div className={cn( "absolute -inset-[1px] z-0 rounded-lg border", onMouseEnter && "opacity-75 transition-opacity group-hover:opacity-100", { - "border-2": resolution, + "border-2": !isNil(resolution), } )} style={{ display: !isNil(resolution) && !isResolutionSuccessful ? "none" : "block", width: borderOnly ? `calc(100% + ${WIDTH_ADJUSTMENT}px)` : progress < 3 ? "3%" : `calc(${progress}% + ${WIDTH_ADJUSTMENT}px)`, background: (() => { - if (borderOnly || resolution) { + if (borderOnly || !isNil(resolution)) { return "transparent"; } if (isClosed && !forceColorful) { return addOpacityToHex( mounted ? getThemeColor(METAC_COLORS.gray["500"]) : METAC_COLORS.gray["500"].DEFAULT, 0.4 ); } return addOpacityToHex( mounted ? getThemeColor(color) : color.DEFAULT, 0.4 ); })(), borderColor: (() => { - if (resolution) { + if (!isNil(resolution)) { return mounted ? getThemeColor(METAC_COLORS.purple["700"]) : METAC_COLORS.gray["700"].DEFAULT; }🤖 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/consumer_post_card/group_forecast_card/forecast_choice_bar.tsx` around lines 115 - 159, The overlay styling incorrectly treats numeric 0 as falsy where it checks "if (resolution)" in the inline style functions, causing resolved rows with resolution === 0 to render as unresolved; update those truthy checks in the background and borderColor branches to use the same guard used earlier (e.g., !isNil(resolution) or explicit resolution !== null && resolution !== undefined) so 0 is treated as a valid resolved value, leaving the existing !isNil(resolution) display guard unchanged and referencing the same resolution variable inside the style object in forecast_choice_bar.tsx.
🤖 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/messages/pt.json`:
- Line 1560: The Portuguese translation for "fanChart" is inconsistent with the
existing label "fanGraph"; change the value of the "fanChart" key to match the
existing capitalization and wording used for "fanGraph" (i.e., "Gráfico de
Leque") so both keys use identical UI copy; update the "fanChart" entry in
front_end/messages/pt.json accordingly.
In `@front_end/src/app/`(main)/questions/[id]/components/group_timeline.tsx:
- Around line 177-186: The external highlight effect currently mutates
choiceItems state in useEffect (the block that checks externalHighlightedChoice
and calls setChoiceItems), which allows later state updates (e.g., from
onChoiceItemsUpdate or BE resync) to drop the external highlight; remove that
useEffect and stop storing highlighted in state, and instead compute the
highlighted flag at render time by mapping choiceItems to a derived list (e.g.,
itemsWithHighlight = choiceItems.map(item => ({...item, highlighted: item.choice
=== externalHighlightedChoice})) ) and pass itemsWithHighlight to the chart/list
renderers or callers; ensure any code that relied on choiceItems' stored
highlighted uses the derived itemsWithHighlight and keep
externalHighlightedChoice as the single source of truth for highlight.
In
`@front_end/src/app/`(main)/questions/[id]/components/multiple_choices_chart_view/compact_legend_bar/index.tsx:
- Around line 20-23: The isResolvedNo function currently checks the localized
displayedResolution ("No") which breaks localization; replace the localized
check with the ChoiceItem's canonical resolution field (e.g., item.resolution or
item.resolutionCode) instead of item.displayedResolution and compare against the
canonical "no" value or the project's resolution enum (use the same "no" token
used in the Binary branch or QuestionResolution.No if available) so both
branches use a locale-independent value; update isResolvedNo to reference that
canonical field and remove the hardcoded "No" string.
In
`@front_end/src/app/`(main)/questions/[id]/components/multiple_choices_chart_view/index.tsx:
- Around line 95-98: The legend currently only toggles isCursorOverLegend via
mouse events (legendEnterProps), so touch interactions still allow the floating
tooltip to appear; add corresponding touch handlers (onTouchStart/onTouchEnd or
onPointerDown/onPointerUp with pointerType checks) to the same props used for
the legend (e.g., legendEnterProps and the similar legendSvgProps block around
lines 406-410) to set isCursorOverLegend true on touch start/press and false on
touch end/cancel, ensuring the tooltip logic checks isCursorOverLegend to
suppress showing the floating tooltip during touch interactions.
In
`@front_end/src/app/`(main)/questions/[id]/components/question_page_shell/meta_row.tsx:
- Line 145: The component currently appends a hardcoded "..." after t("nMore", {
count: hiddenChips.length }) which breaks localization; change the usage so the
entire label (including any trailing punctuation) comes from i18n—e.g., replace
t("nMore", { count: hiddenChips.length }) + "..." with a single call like
t("nMoreWithSuffix", { count: hiddenChips.length }) or t("nMore", { count:
hiddenChips.length, suffix: "..." }) and then add/update the corresponding
translation keys in your locale files to include the ellipsis/formatting; remove
the hardcoded "..." in meta_row.tsx (referencing the hiddenChips and t(...)
usage) so translators control the full output.
In
`@front_end/src/app/`(main)/questions/[id]/components/question_view/consumer_question_view/consumer_list_chart_shell.tsx:
- Around line 65-77: The chart pane is forced hidden on mobile due to the fixed
"hidden sm:flex" class, so when hideListOnMobile is true both panes vanish;
update the second div's className logic in consumer_list_chart_shell.tsx to
remove the "hidden" on small screens when hideListOnMobile is true (i.e.,
conditionally omit "hidden" based on the hideListOnMobile prop) so the chart
remains visible on mobile; keep the existing responsive classes (sm:flex
sm:flex-col) and preserve other conditional classes (reduceInnerPadding,
hideDivider, isExpanded) unchanged.
In `@front_end/src/components/charts/group_chart.tsx`:
- Around line 626-630: The endpoint marker logic creates a fake baseline marker
by coercing missing final y to 0; instead detect when the last point's y is
null/undefined and skip creating/rendering the endpoint marker. Update the point
construction in group_chart.tsx (the const point using isClosed, line, xDomain)
to only set y when line?.at(-1)?.y is a number (otherwise make the point
undefined or signal to not render), and apply the same change to the duplicated
block around the other occurrence (the similar code at the 655-660 region) so
markers are omitted when the latest y is missing.
In `@front_end/src/components/charts/numeric_chart.tsx`:
- Around line 607-609: The touch tooltip anchor isn't cleared on touch cancel,
leaving stale state; add an onTouchCancel handler on the same element that
clears touchPoint (same behavior as handleTouchEnd) or call the existing
handleTouchEnd from a new handleTouchCancel function; update the component to
either pass onTouchCancel={handleTouchEnd} or implement handleTouchCancel to
call setTouchPoint(null)/reset any tooltip state (refer to touchPoint,
handleTouchEnd, and setTouchPoint in numeric_chart.tsx).
- Around line 859-862: The value-box conditional starting at {!isDiamondActive
&& !isNil(highlightedPoint) && !hideCP && !(hideCursorValueLabel &&
isCursorActive) ? ( currently allows the cursor value box in continuous consumer
dot mode; add an explicit guard for that mode (e.g., &&
!isContinuousConsumerDotMode or the actual prop/flag used to indicate continuous
consumer dot interaction) so the value-box does not render in dot-only mode;
update the conditional around the value-box render in numeric_chart.tsx (the
ternary that uses isDiamondActive, highlightedPoint, hideCP,
hideCursorValueLabel, isCursorActive) to include this extra check.
In
`@front_end/src/components/consumer_post_card/group_forecast_card/forecast_card_wrapper.tsx`:
- Around line 56-74: The minimal branch currently renders a non-interactive div
so hidden rows cannot be expanded; change the element in the isMinimal branch
(where showExpandRow is true and toggleButtonClassName is used) from a div to an
interactive button element, give it type="button", attach the same onClick
handler (onExpand) and aria-pressed={false} (and keep the FontAwesomeIcon and
t("otherWithCount", { count: otherItemsCount }) content) so the minimal variant
becomes clickable and accessible like the non-minimal branch.
In
`@front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx`:
- Line 42: The percentage rows in PercentageForecastCard are not updating the
hover-sync context: add mouse handlers on each percentage bar row to call the
hover-context setter (the same hoveredChoiceName setter used by the other row
types) so entering a row sets hoveredChoiceName to that choice's name and
leaving clears it; locate where setIsExpanded is pulled from
useListChartExpanded() and also import/consume the hover-sync setter (the
hoveredChoiceName setter) and wire it into the row elements rendered around
lines ~42 and ~95-109 so side-by-side charts/legends cross-highlight correctly.
---
Outside diff comments:
In `@front_end/src/components/charts/group_chart.tsx`:
- Around line 383-387: The inPlot check currently uses x >= 0 which wrongly
includes the left gutter; change the horizontal lower bound to the left gutter
variable (use leftPadding or the existing left gutter constant) so the condition
becomes x >= leftPadding (and keep x <= chartWidth - maxRightPadding), and apply
this same fix in the mouse handler and both touch handlers where inPlot is
computed so hover/touch sync and cursor clamping only activate inside the actual
drawable plot area.
In
`@front_end/src/components/consumer_post_card/group_forecast_card/forecast_choice_bar.tsx`:
- Around line 115-159: The overlay styling incorrectly treats numeric 0 as falsy
where it checks "if (resolution)" in the inline style functions, causing
resolved rows with resolution === 0 to render as unresolved; update those truthy
checks in the background and borderColor branches to use the same guard used
earlier (e.g., !isNil(resolution) or explicit resolution !== null && resolution
!== undefined) so 0 is treated as a valid resolved value, leaving the existing
!isNil(resolution) display guard unchanged and referencing the same resolution
variable inside the style object in forecast_choice_bar.tsx.
🪄 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: 5cad9e9c-f5f0-40e6-bfe8-6d2cba18340a
📒 Files selected for processing (53)
front_end/messages/cs.jsonfront_end/messages/en.jsonfront_end/messages/es.jsonfront_end/messages/pt.jsonfront_end/messages/zh-TW.jsonfront_end/messages/zh.jsonfront_end/src/app/(main)/questions/[id]/components/group_timeline.tsxfront_end/src/app/(main)/questions/[id]/components/key_factors/key_factors_question_consumer_section.tsxfront_end/src/app/(main)/questions/[id]/components/multiple_choices_chart_view/compact_legend_bar/index.tsxfront_end/src/app/(main)/questions/[id]/components/multiple_choices_chart_view/index.tsxfront_end/src/app/(main)/questions/[id]/components/question_page_shell/index.tsxfront_end/src/app/(main)/questions/[id]/components/question_page_shell/meta_row.tsxfront_end/src/app/(main)/questions/[id]/components/question_page_shell/title_row.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_group_chart.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_list_chart_shell.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_time_series_pane.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/fan_graph_chart_panel.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/prediction/group_of_questions_prediction/index.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/prediction/index.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/prediction/single_question_prediction/continuous_question_prediction.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/timeline/index.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/forecaster_question_view/question_header/question_header_cp_status.tsxfront_end/src/app/(main)/questions/[id]/components/sidebar/similar_questions/similar_question_prediction_chip.tsxfront_end/src/components/charts/fan_chart.tsxfront_end/src/components/charts/group_chart.tsxfront_end/src/components/charts/histogram.tsxfront_end/src/components/charts/multiple_choice_chart.tsxfront_end/src/components/charts/numeric_chart.tsxfront_end/src/components/charts/numeric_timeline.tsxfront_end/src/components/charts/primitives/chart_container.tsxfront_end/src/components/charts/primitives/chart_fan_tooltip.tsxfront_end/src/components/charts/primitives/line_cursor_points.tsxfront_end/src/components/consumer_post_card/binary_cp_bar.tsxfront_end/src/components/consumer_post_card/consumer_question_tile/consumer_continuous_tile.tsxfront_end/src/components/consumer_post_card/group_forecast_card/date_forecast_card/index.tsxfront_end/src/components/consumer_post_card/group_forecast_card/date_forecast_card/scatter_label.tsxfront_end/src/components/consumer_post_card/group_forecast_card/forecast_card_wrapper.tsxfront_end/src/components/consumer_post_card/group_forecast_card/forecast_choice_bar.tsxfront_end/src/components/consumer_post_card/group_forecast_card/index.tsxfront_end/src/components/consumer_post_card/group_forecast_card/numeric_forecast_card.tsxfront_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsxfront_end/src/components/consumer_post_card/index.tsxfront_end/src/components/consumer_post_card/time_series_chart/index.tsxfront_end/src/components/consumer_post_card/time_series_chart/time_series_label.tsxfront_end/src/components/detailed_question_card/detailed_group_card/index.tsxfront_end/src/components/detailed_question_card/detailed_question_card/continuous_chart_card.tsxfront_end/src/components/detailed_question_card/detailed_question_card/hooks/use_full_aggregation.tsfront_end/src/components/detailed_question_card/detailed_question_card/index.tsxfront_end/src/components/detailed_question_card/detailed_question_card/multiple_choice_chart_card.tsxfront_end/src/components/post_card/question_tile/continuous_cp_bar.tsxfront_end/src/contexts/continuous_chart_cursor_context.tsxfront_end/src/hooks/use_chart_tooltip.tsfront_end/src/hooks/use_container_size.ts
…ing, and null endpoint guards across consumer chart components
782f483
into
feat/question-page-redesign-2nd-iteration
* Mount consumer binary + continuous side-by-side with the detailed chart card & Timeline / Histogram toggle on binary bodies (#4692) * feat: add QuestionHeaderCPStatus next to timeline for consumer binary questions * fix: remove extra mt-8 top margin from consumer continuous timeline * feat: add Timeline/Histogram toggle to binary detailed chart cards and fix consumer binary radial padding * fix: resolve layout jumping between chart views, fix histogram full-width responsive scaling, and center binary radial charts on mobile * fix: prevent chart layout snapping with synchronous measurements, enable side-by-side continuous charts on sm breakpoints, and standardize spacing utility classes * fix: show fallback message when histogram tab has no data * Live cursor updates the side-by-side left card (#4694) * feat: live cursor updates left-panel value, range, and distribution for continuous consumer view, with dot indicator on timeline and no tooltip/dashed line in consumer mode * feat: animate consumer mobile mini chart on timeline cursor and fix mobile tooltip position tracking * fix: always serialize full forecast_values in aggregation history for cursor-time distribution animation * fix: memoize cursorChartData to skip cdfToPmf recalculation when forecast_values reference is unchanged * fix: resolve cursor dot color override through resolveToCssColor * refactor: lazily fetch full aggregation history from aggregation explorer on pointer enter for continuous consumer cursor animation, keeping posts endpoint lightweight * fix: pass include_bots_in_aggregates to aggregation explorer to match stored aggregation bot semantics, format aggregate_forecasts.py * fix: use stable setActiveForecast reference in effect deps to prevent infinite re-render loop on hover * Empty / pre-CP-reveal audit for the new side-by-side row (#4700) * fix: handle hideCP, cpRevealsOn, and isEmpty in the side-by-side left card for binary and continuous questions * fix: handle isEmpty, cpRevealsOn, and hideCP states in the side-by-side layout left panel and fix title overflow onto the CP block * fix: refine mobile hidden-CP states for binary and continuous in the forecaster title row and left panel * fix: prevent CP status from being squeezed off-screen in the mobile title row * fix: restore "No forecasts yet" overlay in feed cards and suppress it on the question detail page * fix: wire hideCP, cpRevealsOn, and isEmpty states into all question-type left panels for the side-by-side layout * fix: replace hardcoded isEmpty:false with suppressEmptyOverlay prop, fix missing useMemo deps, and align binary cpRevealsOn state with continuous * fix: correct import order in continuous_question_prediction.tsx * feat: enable live cursor sync on continuous questions for forecaster view * feat: bridge timeline cursor to forecaster mobile CP status chip * fix: hide cursor tooltip for continuous questions in forecaster view * fix: propagate enriched aggregation into timeline and histogram renders; fix zh-TW locale wording for histogram empty state * Question Page Redesign - Multiple choice + binary group + continuous group + time series (#4734) * Consumer side-by-side shell, row restyle, and expand/collapse for N-row question types (#4712) * feat: add ConsumerListChartShell for N-row consumer side-by-side layout * fix: remove extra top/bottom margin on group prediction inside the side-by-side shell and fix styles in ConsumerListChartShell * feat: restyle consumer MC and group forecast rows with chevron expand/collapse and expanded panel * feat: restyle sidebar expand button with ellipsis/border variant and hide legend in consumer MC and binary-group chart views * fix: restore isBordered prop in ForecastChoiceBar and show TimeSeriesChart in fan graph left panel * fix: stretch group forecast card to full width and use minimal expand button in feed * feat: add consumer side-by-side shell for continuous numeric group questions with proportional bars, hover-synced chart highlight, and endpoint dots * fix: scope bar hover effects to wired handlers, fill height for all bar-row types, cover discrete groups, and prevent expand height jump * fix: render minimal expand indicator as div to fix accessibility violation * fix: remove expand button from DOM and clamp hiddenCount when overlay is open * Compact legend bar for MC + binary group forecaster (#4724) * feat: add compact legend bar above MC and binary group chart with per-option checkbox, current %, and resolved-no strikethrough * feat: responsive compact legend bar for MC and binary group charts with mobile item cap, resolved-no collapsing, tooltip suppression, and hidden zoom buttons on mobile * fix: use MC resolved-no detection in binary timeline view * fix: remove pointer/hover affordances from resolved-NO legend items and add aria-label to checkboxes * Time series visualisation updates (#4738) * feat: add consumer Fan/Timeline toggle with side-by-side bar chart, hover sync, and color-aligned time series visualisation for FanGraph * fix: enable mobile fan chart tooltip in consumer view due to async timing issue that kept the tooltip invisible * fix: support touch drag tracking on fan chart tooltip and simplify tooltip rendering * fix: fan chart toggle order and default differ between consumer and forecaster views * fix: mobile fan chart tooltip: matches desktop hover behavior, dismisses outside tap, no page shift on drag * fix: group timeline touch tooltip now works on first tap, follows finger drag, and shows endpoint dots on all lines * fix: duplicate fan chart tooltip on desktop hover * fix: handle touchcancel, sticky tooltip/cursor state, and bar highlight edge cases across charts * Date group consumer visual restyle (#4747) * feat: date group consumer visual restyle with iter 3 side-by-side layout (bars left, scatter chart right) * fix: scope date group visual changes to question page only; border-only bars in mobile timeline tab * refactor: use cn() for className in DateForecastCard * fix: derive highlight state, canonical resolution checks, touch handling, and null endpoint guards across consumer chart components
* Mount consumer binary + continuous side-by-side with the detailed chart card & Timeline / Histogram toggle on binary bodies (#4692) * feat: add QuestionHeaderCPStatus next to timeline for consumer binary questions * fix: remove extra mt-8 top margin from consumer continuous timeline * feat: add Timeline/Histogram toggle to binary detailed chart cards and fix consumer binary radial padding * fix: resolve layout jumping between chart views, fix histogram full-width responsive scaling, and center binary radial charts on mobile * fix: prevent chart layout snapping with synchronous measurements, enable side-by-side continuous charts on sm breakpoints, and standardize spacing utility classes * fix: show fallback message when histogram tab has no data * Live cursor updates the side-by-side left card (#4694) * feat: live cursor updates left-panel value, range, and distribution for continuous consumer view, with dot indicator on timeline and no tooltip/dashed line in consumer mode * feat: animate consumer mobile mini chart on timeline cursor and fix mobile tooltip position tracking * fix: always serialize full forecast_values in aggregation history for cursor-time distribution animation * fix: memoize cursorChartData to skip cdfToPmf recalculation when forecast_values reference is unchanged * fix: resolve cursor dot color override through resolveToCssColor * refactor: lazily fetch full aggregation history from aggregation explorer on pointer enter for continuous consumer cursor animation, keeping posts endpoint lightweight * fix: pass include_bots_in_aggregates to aggregation explorer to match stored aggregation bot semantics, format aggregate_forecasts.py * fix: use stable setActiveForecast reference in effect deps to prevent infinite re-render loop on hover * Empty / pre-CP-reveal audit for the new side-by-side row (#4700) * fix: handle hideCP, cpRevealsOn, and isEmpty in the side-by-side left card for binary and continuous questions * fix: handle isEmpty, cpRevealsOn, and hideCP states in the side-by-side layout left panel and fix title overflow onto the CP block * fix: refine mobile hidden-CP states for binary and continuous in the forecaster title row and left panel * fix: prevent CP status from being squeezed off-screen in the mobile title row * fix: restore "No forecasts yet" overlay in feed cards and suppress it on the question detail page * fix: wire hideCP, cpRevealsOn, and isEmpty states into all question-type left panels for the side-by-side layout * fix: replace hardcoded isEmpty:false with suppressEmptyOverlay prop, fix missing useMemo deps, and align binary cpRevealsOn state with continuous * fix: correct import order in continuous_question_prediction.tsx * feat: enable live cursor sync on continuous questions for forecaster view * feat: bridge timeline cursor to forecaster mobile CP status chip * fix: hide cursor tooltip for continuous questions in forecaster view * fix: propagate enriched aggregation into timeline and histogram renders; fix zh-TW locale wording for histogram empty state * Question Page Redesign - Multiple choice + binary group + continuous group + time series (#4734) * Consumer side-by-side shell, row restyle, and expand/collapse for N-row question types (#4712) * feat: add ConsumerListChartShell for N-row consumer side-by-side layout * fix: remove extra top/bottom margin on group prediction inside the side-by-side shell and fix styles in ConsumerListChartShell * feat: restyle consumer MC and group forecast rows with chevron expand/collapse and expanded panel * feat: restyle sidebar expand button with ellipsis/border variant and hide legend in consumer MC and binary-group chart views * fix: restore isBordered prop in ForecastChoiceBar and show TimeSeriesChart in fan graph left panel * fix: stretch group forecast card to full width and use minimal expand button in feed * feat: add consumer side-by-side shell for continuous numeric group questions with proportional bars, hover-synced chart highlight, and endpoint dots * fix: scope bar hover effects to wired handlers, fill height for all bar-row types, cover discrete groups, and prevent expand height jump * fix: render minimal expand indicator as div to fix accessibility violation * fix: remove expand button from DOM and clamp hiddenCount when overlay is open * Compact legend bar for MC + binary group forecaster (#4724) * feat: add compact legend bar above MC and binary group chart with per-option checkbox, current %, and resolved-no strikethrough * feat: responsive compact legend bar for MC and binary group charts with mobile item cap, resolved-no collapsing, tooltip suppression, and hidden zoom buttons on mobile * fix: use MC resolved-no detection in binary timeline view * fix: remove pointer/hover affordances from resolved-NO legend items and add aria-label to checkboxes * Time series visualisation updates (#4738) * feat: add consumer Fan/Timeline toggle with side-by-side bar chart, hover sync, and color-aligned time series visualisation for FanGraph * fix: enable mobile fan chart tooltip in consumer view due to async timing issue that kept the tooltip invisible * fix: support touch drag tracking on fan chart tooltip and simplify tooltip rendering * fix: fan chart toggle order and default differ between consumer and forecaster views * fix: mobile fan chart tooltip: matches desktop hover behavior, dismisses outside tap, no page shift on drag * fix: group timeline touch tooltip now works on first tap, follows finger drag, and shows endpoint dots on all lines * fix: duplicate fan chart tooltip on desktop hover * fix: handle touchcancel, sticky tooltip/cursor state, and bar highlight edge cases across charts * Date group consumer visual restyle (#4747) * feat: date group consumer visual restyle with iter 3 side-by-side layout (bars left, scatter chart right) * fix: scope date group visual changes to question page only; border-only bars in mobile timeline tab * refactor: use cn() for className in DateForecastCard * fix: derive highlight state, canonical resolution checks, touch handling, and null endpoint guards across consumer chart components
Closes #4643
Summary
This PR delivers the third iteration of the Question Page redesign by extending the side-by-side consumer layout architecture across all N-row question types, introducing new shared chart/list primitives, improving chart interactions and hover synchronization, and refining forecast card, legend, and timeline behaviors across both Consumer and Forecaster views.
Implemented in Iteration 3
ConsumerListChartShellarchitecture to support all N-row body types:QuestionPageShellrouting logic to use explicit per-type rendering branches instead of shared conditional groupingConsumer side-by-side layouts & chart integration
ConsumerGroupChart,ConsumerTimeSeriesPane, andFanGraphChartPanelas new shared visualization primitivesGroupTimeline,QuestionTimeline, scatter charts, and fan charts directly inside the shared shell layoutshideCP, replacing left-panel forecast content withRevealCPButtonacross all supported body typesForecast card & list interaction improvements
buttonVariantsupport (primaryandminimal)fillHeightsupport so forecast rows scale vertically to fill available panel spaceborderOnlyforecast bars for date group questions to represent ranges instead of probabilitiesHover synchronization & chart highlighting
TimeSeriesChartinto per-questionVictoryBarlayers to support independent hover opacity behaviorLegend bar & chart header improvements
CompactLegendBarfor Forecaster MC and Binary Group chartsChoiceItem.activestate{n} moreoverflow controlDate group visualization updates
ConsumerListChartShellhideListOnMobilehideDividerreduceInnerPaddingFunctional improvements & fixes
MultiChoicesChartViewlegendContainerRefCleanup & refactors
Summary by CodeRabbit
New Features
Improvements
Localization