updates for labor hub v4#4635
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 41 minutes and 47 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR enhances the labor-hub UI with improved chart interactions, updated visual indicators, new utility color functions, and metadata display improvements. Changes include adding a "Forecasts: Live" indicator to the hero section, refactoring sort indicators with new visual states, implementing APCA-based contrast color selection, improving marker hit-area behavior, updating external links, and minor formatting adjustments. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~28 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@front_end/src/components/charts/primitives/timeline_markers/group_timeline_markers_overlay.tsx`:
- Line 5: The VictoryPortal should not be a direct child of VictoryChart;
instead keep VictoryScatter (from group_timeline_markers_overlay.tsx) as a
direct child and move the portal into the scatter's dataComponent so the cloned
props (domain/scale) are preserved; replace the current
VictoryPortal-wrapping-child approach by configuring VictoryScatter's
dataComponent to be a VictoryPortal that renders TimelineMarkerPoint (so
TimelineMarkerPoint is portaled but VictoryScatter remains a chart child and
receives the injected props).
In `@front_end/src/utils/core/colors.ts`:
- Around line 67-90: The parseHexColor function accepts malformed hex channels
(e.g., "#0g0000") because Number.parseInt can silently parse partial values;
update parseHexColor to validate the normalized/expanded hex string with a
strict regex (only 6 hex digits 0-9a-fA-F) before calling Number.parseInt and
return null if it fails, ensuring parseHexColor (used by
pickHighestContrastTextColor) never returns incorrect RGB values from malformed
input.
🪄 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: d552a72b-817a-4b5e-a794-d2d667528f09
📒 Files selected for processing (7)
front_end/src/app/(main)/labor-hub/components/labor_hub_chart_hover_context.tsxfront_end/src/app/(main)/labor-hub/components/question_cards/multi_line_chart.tsxfront_end/src/app/(main)/labor-hub/sections/hero.tsxfront_end/src/app/(main)/labor-hub/sections/methodology.tsxfront_end/src/app/(main)/labor-hub/sections/sortable_research_table.tsxfront_end/src/components/charts/primitives/timeline_markers/group_timeline_markers_overlay.tsxfront_end/src/utils/core/colors.ts
| /** Parses `#RGB` / `#RRGGBB` (with optional leading `#`). Returns null if not a 6-digit hex. */ | ||
| export function parseHexColor(color: string): RGBColor | null { | ||
| const normalizedColor = color.replace("#", ""); | ||
| const hex = | ||
| normalizedColor.length === 3 | ||
| ? normalizedColor | ||
| .split("") | ||
| .map((value) => `${value}${value}`) | ||
| .join("") | ||
| : normalizedColor; | ||
|
|
||
| if (hex.length !== 6) { | ||
| return null; | ||
| } | ||
|
|
||
| const red = Number.parseInt(hex.slice(0, 2), 16); | ||
| const green = Number.parseInt(hex.slice(2, 4), 16); | ||
| const blue = Number.parseInt(hex.slice(4, 6), 16); | ||
|
|
||
| if ([red, green, blue].some(Number.isNaN)) { | ||
| return null; | ||
| } | ||
|
|
||
| return [red, green, blue]; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify JavaScript hex parsing behavior for malformed channels.
node - <<'NODE'
console.log('parseInt("0g", 16) =', Number.parseInt("0g", 16));
console.log('Expected: a regex gate is needed because this prints 0, not NaN.');
NODERepository: Metaculus/metaculus
Length of output: 150
🏁 Script executed:
# Check if the file exists and matches the review snippet
cat -n front_end/src/utils/core/colors.ts | sed -n '60,95p'Repository: Metaculus/metaculus
Length of output: 1330
🏁 Script executed:
# Search for usage of parseHexColor in the codebase
rg "parseHexColor" --type typescript --type tsx -nRepository: Metaculus/metaculus
Length of output: 97
🏁 Script executed:
# Verify the specific behavior with test cases
node - <<'NODE'
// Test the current behavior with malformed hex
function parseHexColorOld(color) {
const normalizedColor = color.replace("#", "");
const hex =
normalizedColor.length === 3
? normalizedColor.split("").map((value) => `${value}${value}`).join("")
: normalizedColor;
if (hex.length !== 6) {
return null;
}
const red = Number.parseInt(hex.slice(0, 2), 16);
const green = Number.parseInt(hex.slice(2, 4), 16);
const blue = Number.parseInt(hex.slice(4, 6), 16);
if ([red, green, blue].some(Number.isNaN)) {
return null;
}
return [red, green, blue];
}
console.log("Test 1 - Valid `#RGB`: `#F0A`");
console.log(parseHexColorOld("#F0A"));
console.log("\nTest 2 - Valid `#RRGGBB`: `#FF00AA`");
console.log(parseHexColorOld("#FF00AA"));
console.log("\nTest 3 - Malformed (invalid hex char): `#0g0000`");
console.log(parseHexColorOld("#0g0000"));
console.log("\nTest 4 - Malformed (# in middle): F#F0AA");
console.log(parseHexColorOld("F#F0AA"));
console.log("\nTest 5 - Malformed (all invalid): `#gggggg`");
console.log(parseHexColorOld("#gggggg"));
NODERepository: Metaculus/metaculus
Length of output: 308
🏁 Script executed:
# Search for usage of parseHexColor (file type detection failed, try simple grep)
rg "parseHexColor" -nRepository: Metaculus/metaculus
Length of output: 348
Validate hex format before parsing to reject malformed channels.
Number.parseInt(..., 16) accepts partial hex like "0g" as 0, not NaN. The current NaN check only catches complete invalidity. For example, parseHexColor("#0g0000") returns [0, 0, 0] instead of null, silently producing incorrect RGB values. This can skew contrast calculations in pickHighestContrastTextColor (lines 108–109).
Use regex to validate the full hex string before parsing:
Proposed fix
/** Parses `#RGB` / `#RRGGBB` (with optional leading `#`). Returns null if not a 6-digit hex. */
export function parseHexColor(color: string): RGBColor | null {
- const normalizedColor = color.replace("#", "");
+ const match = color.match(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
+ if (!match) {
+ return null;
+ }
+
+ const normalizedColor = match[1];
const hex =
normalizedColor.length === 3
? normalizedColor
.split("")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** Parses `#RGB` / `#RRGGBB` (with optional leading `#`). Returns null if not a 6-digit hex. */ | |
| export function parseHexColor(color: string): RGBColor | null { | |
| const normalizedColor = color.replace("#", ""); | |
| const hex = | |
| normalizedColor.length === 3 | |
| ? normalizedColor | |
| .split("") | |
| .map((value) => `${value}${value}`) | |
| .join("") | |
| : normalizedColor; | |
| if (hex.length !== 6) { | |
| return null; | |
| } | |
| const red = Number.parseInt(hex.slice(0, 2), 16); | |
| const green = Number.parseInt(hex.slice(2, 4), 16); | |
| const blue = Number.parseInt(hex.slice(4, 6), 16); | |
| if ([red, green, blue].some(Number.isNaN)) { | |
| return null; | |
| } | |
| return [red, green, blue]; | |
| /** Parses `#RGB` / `#RRGGBB` (with optional leading `#`). Returns null if not a 6-digit hex. */ | |
| export function parseHexColor(color: string): RGBColor | null { | |
| const match = color.match(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/); | |
| if (!match) { | |
| return null; | |
| } | |
| const normalizedColor = match[1]; | |
| const hex = | |
| normalizedColor.length === 3 | |
| ? normalizedColor | |
| .split("") | |
| .map((value) => `${value}${value}`) | |
| .join("") | |
| : normalizedColor; | |
| if (hex.length !== 6) { | |
| return null; | |
| } | |
| const red = Number.parseInt(hex.slice(0, 2), 16); | |
| const green = Number.parseInt(hex.slice(2, 4), 16); | |
| const blue = Number.parseInt(hex.slice(4, 6), 16); | |
| if ([red, green, blue].some(Number.isNaN)) { | |
| return null; | |
| } | |
| return [red, green, blue]; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@front_end/src/utils/core/colors.ts` around lines 67 - 90, The parseHexColor
function accepts malformed hex channels (e.g., "#0g0000") because
Number.parseInt can silently parse partial values; update parseHexColor to
validate the normalized/expanded hex string with a strict regex (only 6 hex
digits 0-9a-fA-F) before calling Number.parseInt and return null if it fails,
ensuring parseHexColor (used by pickHighestContrastTextColor) never returns
incorrect RGB values from malformed input.
Cleanup: Preview Environment RemovedThe preview environment for this PR has been destroyed.
Cleanup triggered by PR close at 2026-04-21T08:29:35Z |
Summary by CodeRabbit
Release Notes
New Features
Improvements