Skip to content

Fix: UI polish and stale forecast#4845

Open
ncarazon wants to merge 4 commits into
mainfrom
fix/ui-polish-and-stale-forecast
Open

Fix: UI polish and stale forecast#4845
ncarazon wants to merge 4 commits into
mainfrom
fix/ui-polish-and-stale-forecast

Conversation

@ncarazon

@ncarazon ncarazon commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Resolves #4836
Resolves #4837
Resolves #4842

Summary

Fix 1: Expand gradient fadeout in consumer group/MC views

The fadeout overlay stayed visible even when the list was fully expanded or had no overflow, causing a visual artifact at the bottom.

Implemented changes:

  • ConsumerListChartShell: hide the gradient when scroll is at the bottom or content does not overflow the container.

Fix 2: Fan chart tooltip contained within its parent

The tooltip could overflow outside the chart container on questions with many options or near the edges.

Implemented changes:

  • FanChart: constrained tooltip position to stay within the chart bounding box.

Fix 3: Sidebar forecast % not updating live

Group sub-question percentages in the similar questions sidebar showed stale values even after the community prediction had moved, persisting across refreshes.

Root cause: generateChoiceItemsFromGroupQuestions filters aggregationHistory by closeTime, which excluded the most recent forecast for closed sub-questions. The display value was read from that filtered array instead of latest. Standalone binary questions are unaffected – they read directly from aggregations[method].latest.centers[0] and never touch the filtered history.

Implemented changes:

  • choices.ts: populate latestValue from aggregations[method].latest.centers[0] on each choice item.
  • percentage_forecast_card.tsx, choice_option.tsx, compact_legend_bar: prefer latestValue over the last history entry when rendering the current percentage.

Demo videos

Before

  • Sidebar forecast % not updating live
sidebar-forecast-before.mp4

After

  • Expand gradient fadeout in consumer group/MC views
expand-gradient-fadeout.mp4
  • Fan chart tooltip contained within its parent
fan-chart-tooltip.mp4
  • Sidebar forecast % not updating live
sidebar-forecast.mp4

Summary by CodeRabbit

  • New Features

    • Expanded forecast cards now detect scrollability and show/hide the bottom content indicator.
    • Forecast displays, percentage cards, and choice tiles prefer a provided latest value when available; components accept an optional latest value.
  • Style

    • Chart container overflow adjusted to allow visible overflow.
    • Disabled slider rail styling adjusted for consistent theming.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

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: 45172e44-b56f-450f-8978-40b0333cc139

📥 Commits

Reviewing files that changed from the base of the PR and between 23bac9b and cb6f90d.

📒 Files selected for processing (9)
  • front_end/src/app/(main)/questions/[id]/components/multiple_choices_chart_view/compact_legend_bar/index.tsx
  • front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_list_chart_shell.tsx
  • front_end/src/components/consumer_post_card/group_forecast_card/forecast_card_wrapper.tsx
  • front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx
  • front_end/src/components/forecast_maker/forecast_choice_option.tsx
  • front_end/src/components/post_card/multiple_choice_tile/choice_option.tsx
  • front_end/src/components/post_card/multiple_choice_tile/multiple_choice_tile_legend.tsx
  • front_end/src/types/choices.ts
  • front_end/src/utils/questions/choices.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx
  • front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_list_chart_shell.tsx
  • front_end/src/components/post_card/multiple_choice_tile/multiple_choice_tile_legend.tsx
  • front_end/src/app/(main)/questions/[id]/components/multiple_choices_chart_view/compact_legend_bar/index.tsx
  • front_end/src/components/forecast_maker/forecast_choice_option.tsx
  • front_end/src/types/choices.ts
  • front_end/src/components/post_card/multiple_choice_tile/choice_option.tsx
  • front_end/src/components/consumer_post_card/group_forecast_card/forecast_card_wrapper.tsx
  • front_end/src/utils/questions/choices.ts

📝 Walkthrough

Walkthrough

Adds optional latestValue to choice items and uses it across UI components to prefer live forecast percentages, implements scroll detection to conditionally show/hide the bottom gradient in expanded forecast cards, and tweaks container overflow and slider theming.

Changes

Latest Value Display and UI Refinements

Layer / File(s) Summary
Type definition and data generation
front_end/src/types/choices.ts, front_end/src/utils/questions/choices.ts
ChoiceItem type extended with optional latestValue. Choice generator sets latestValue from the latest aggregation center when present.
Choice option component prop and flow
front_end/src/components/post_card/multiple_choice_tile/choice_option.tsx, front_end/src/components/post_card/multiple_choice_tile/multiple_choice_tile_legend.tsx
ChoiceOption accepts latestValue and uses it as the displayed value when cursor timestamp is absent; hasValue is recomputed from that display value. MultipleChoiceTileLegend extracts and forwards latestValue (or undefined when hidden).
Forecast card value display
front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx, front_end/src/app/(main)/questions/[id]/components/multiple_choices_chart_view/compact_legend_bar/index.tsx
PercentageForecastCard and CompactLegendBar now prefer latestValue when computing displayed percentages, falling back to the last aggregation value if needed.
Scroll detection and gradient management
front_end/src/components/consumer_post_card/group_forecast_card/forecast_card_wrapper.tsx
ForecastCardWrapper adds scrollRef and canScrollDown, observes resizes, listens for scroll, computes canScrollDown, and toggles the bottom gradient opacity based on scroll position.
Container overflow and theme styling
front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_list_chart_shell.tsx, front_end/src/components/forecast_maker/forecast_choice_option.tsx
ConsumerListChartShell changes chart container overflow from hidden to visible. ForecastChoiceOption uses a raw gray color pre-mount and a theme-resolved color post-mount for the disabled slider rail.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

Suggested Reviewers

  • elisescu
  • hlbmtc
  • cemreinanc

Poem

🐰 New numbers hop into view at dawn,
Fresh latestValue where old percents yawn.
Scroll the card — the gradient fades away,
Live forecasts leap forward, bright as day.
A little rabbit cheers, “Ship it — hip hooray!”

🚥 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 'Fix: UI polish and stale forecast' is partially related to the changeset, as it addresses stale forecast data and UI improvements, but it lacks specificity about the three distinct fixes (gradient fadeout, tooltip containment, and stale percentages).
Linked Issues check ✅ Passed All code changes directly address the three linked issues: gradient fadeout fix [#4836], tooltip containment implementation [#4837], and stale forecast percentage resolution [#4842].
Out of Scope Changes check ✅ Passed All changes are scoped to the three linked issues; no out-of-scope modifications detected. Changes include gradient visibility logic, scroll detection, latestValue propagation, and component prop updates.

✏️ 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 fix/ui-polish-and-stale-forecast

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx (1)

64-64: ⚡ Quick win

Use a last-non-null fallback for aggregation values here too.

Using .at(-1) can yield null on trailing boundary points and force percent to 0 even when prior data exists. Aligning with the CompactLegendBar fallback avoids this mismatch.

Suggested refactor
-      const valueStr = getPredictionDisplayValue(
-        choice.latestValue ?? choice.aggregationValues.at(-1),
+      const fallbackAggregationValue =
+        choice.aggregationValues.findLast((v) => v != null) ?? null;
+      const valueStr = getPredictionDisplayValue(
+        choice.latestValue ?? fallbackAggregationValue,
         {
           questionType: QuestionType.Binary,
           scaling: choice.scaling,
🤖 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/percentage_forecast_card.tsx`
at line 64, Replace the trailing .at(-1) fallback so it returns the last
non-null aggregation value instead of possibly-null boundary points: in the
expression using choice.latestValue and choice.aggregationValues (e.g., the line
with choice.latestValue ?? choice.aggregationValues.at(-1)), iterate or use a
safe helper to locate the last element in choice.aggregationValues that is not
null/undefined (similar to CompactLegendBar’s fallback) and use that value as
the fallback; ensure the code references choice.latestValue and
choice.aggregationValues and preserves existing semantics when no non-null value
exists.
🤖 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/forecast_card_wrapper.tsx`:
- Line 37: The transient gradient flash is caused by initializing canScrollDown
to true; change the useState initialization in ForecastCardWrapper from
useState(true) to useState(false) so the initial render matches the
non-overflowing expanded state until checkScroll() runs and updates
canScrollDown via setCanScrollDown.

In `@front_end/src/components/post_card/multiple_choice_tile/choice_option.tsx`:
- Around line 60-61: The row/icon dimming happens because hasValue still checks
values.at(-1) while valueAtCursor can come from latestValue; update the hasValue
computation to mirror valueAtCursor's source selection (use the same conditional
between latestValue and values[idx]) or simply set hasValue =
!isNil(valueAtCursor) (referencing valueAtCursor, latestValue, values and idx)
so the visual state aligns with what is displayed.

---

Nitpick comments:
In
`@front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx`:
- Line 64: Replace the trailing .at(-1) fallback so it returns the last non-null
aggregation value instead of possibly-null boundary points: in the expression
using choice.latestValue and choice.aggregationValues (e.g., the line with
choice.latestValue ?? choice.aggregationValues.at(-1)), iterate or use a safe
helper to locate the last element in choice.aggregationValues that is not
null/undefined (similar to CompactLegendBar’s fallback) and use that value as
the fallback; ensure the code references choice.latestValue and
choice.aggregationValues and preserves existing semantics when no non-null value
exists.
🪄 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: ab3c51eb-abd0-4fd7-b514-09b4edd40e16

📥 Commits

Reviewing files that changed from the base of the PR and between f30e16c and e698bdd.

📒 Files selected for processing (9)
  • front_end/src/app/(main)/questions/[id]/components/multiple_choices_chart_view/compact_legend_bar/index.tsx
  • front_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/consumer_list_chart_shell.tsx
  • front_end/src/components/consumer_post_card/group_forecast_card/forecast_card_wrapper.tsx
  • front_end/src/components/consumer_post_card/group_forecast_card/percentage_forecast_card.tsx
  • front_end/src/components/forecast_maker/forecast_choice_option.tsx
  • front_end/src/components/post_card/multiple_choice_tile/choice_option.tsx
  • front_end/src/components/post_card/multiple_choice_tile/multiple_choice_tile_legend.tsx
  • front_end/src/types/choices.ts
  • front_end/src/utils/questions/choices.ts

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Environment

Your preview environment is ready!

Resource Details
🌐 Preview URL https://metaculus-pr-4845-fix-ui-polish-and-stale-foreca-preview.mtcl.cc
📦 Docker Image ghcr.io/metaculus/metaculus:fix-ui-polish-and-stale-forecast-cb6f90d
🗄️ PostgreSQL NeonDB branch preview/pr-4845-fix-ui-polish-and-stale-foreca
Redis Fly Redis mtc-redis-pr-4845-fix-ui-polish-and-stale-foreca

Details

  • Commit: cb6f90dcb8ce4b12c2a7d437b4925bf5bbf3c93e
  • Branch: fix/ui-polish-and-stale-forecast
  • Fly App: metaculus-pr-4845-fix-ui-polish-and-stale-foreca

ℹ️ 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

@ncarazon ncarazon force-pushed the fix/ui-polish-and-stale-forecast branch from e092a03 to bbe0f7a Compare June 5, 2026 14:26
@ncarazon ncarazon marked this pull request as ready for review June 5, 2026 14:33
Comment thread front_end/src/components/post_card/multiple_choice_tile/choice_option.tsx Outdated
@ncarazon ncarazon force-pushed the fix/ui-polish-and-stale-forecast branch from bbe0f7a to 23bac9b Compare June 9, 2026 13:42
@ncarazon ncarazon force-pushed the fix/ui-polish-and-stale-forecast branch from 23bac9b to cb6f90d Compare June 9, 2026 13:44

@cemreinanc cemreinanc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consumer View with HideCP on:

Image

It may not be a huge number of users using both Consumer View and Hide CP options together but still seems broken to me as it hides for binary types and doesnt hide for groups etc.

return raw.map((choice) => {
const valueStr = getPredictionDisplayValue(
choice.aggregationValues.at(-1),
choice.latestValue ?? choice.aggregationValues.at(-1),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this still needs the hidden-CP guard as well. In the consumer feed/storefront card UI, a user with community predictions hidden can still land on this card through GroupForecastCard; the legend path now masks latestValue, but this card computes the displayed text and bar progress directly from choice.latestValue ?? choice.aggregationValues.at(-1). That means an open group/MC card can still show real percentages and filled bars even though the user has asked not to see CP. Could we either pass/read hideCP here and render the hidden state, or avoid using CP values when the provider says CP is hidden?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants