feat: Labor Hub /jobs/ — All Jobs wall + per-job detail pages#4751
feat: Labor Hub /jobs/ — All Jobs wall + per-job detail pages#4751aseckin wants to merge 19 commits into
Conversation
Adds two new consumer-facing routes under the Labor Hub: - /labor-hub/jobs/ — Forecast Wall of 15 occupations with year toggle and hover-only news ticker - /labor-hub/jobs/[slug]/ — statically generated job detail page with breadcrumb, year-stat tiles, forecast chart, Jump To carousel, Felten/MNA/AOE exposure metrics, Curated Insights (tiered: data.ts override → top comments on the job's post → keyword-matched sibling comments), wages + economy-wide hours bento, share card, and Hub CTA Share card PNG and OG meta image are generated through the existing screenshot-service pattern at /og/labor-hub/jobs/[slug]/. No new packages, no changes to the existing Labor Hub dashboard. Per-page JSON-LD (Dataset for job pages, ItemList for All Jobs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 2 of the consumer-friendly job pages, closing prototype-parity gaps and the curated-insights UX feedback: - JetBrains Mono wired through next/font and Tailwind; salmon-900 added to the palette so tile text matches the Claude Design output pixel-for-pixel - Forecast Wall tiles: prototype-exact font sizes / padding / hover overlay; ticker now slides by default with horizontal mask gradients and expands to a 3-line fade-bottom excerpt on hover; tile padding reserves space so the title is never overlapped by the ticker - Hub CTA card simplified to match the surrounding page sections (white bg, no colored border) - Jump To strip: arrows snap-to-edge on click (gradient-carousel clamps to 0 / maxScroll when within half a step) and use bright light-bg buttons in dark mode; first snap point is now exactly scrollLeft=0 after dropping the list's px-2 padding so the left arrow hides correctly - Exposure metrics get a question-circle icon next to each HIGH/MED/ LOW chip with a hover tooltip - Wages / Hours mini-cards centered at md+, bigger numeric values - Bento becomes a 3-step MobileCarousel (Embla) on mobile, grid on desktop - Curated Insights: tiered fallback honours per-job excluded_comment_ids; markdown is stripped down to plain prose (tables, images, HTML entities, escapes); empty-after-strip comments are filtered out; comment-sourced items wrap in an anchor to /questions/<post_id>/#comment-<id> that opens in a new tab, with a visible underline + darker username on hover - All anchors in the new pages explicitly opt out of the global globals.css underline rule Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a Labor Hub Jobs feature: typed job data and lookups, cached fetchers (wall, insights, tickers, wages), Jobs listing and detail pages with OG generation, UI components (wall, nav, metrics, curated insights, share/export), chart coloring/sizing, design-token and font updates, and i18n across locales. ChangesLabor Hub Jobs
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (4)
front_end/messages/zh.json (1)
2242-2242: ⚡ Quick winConsider replacing mathematical Unicode character with standard ASCII.
The mathematical bold capital X "𝕏" (U+1D54F) may not render correctly on all systems and fonts. For better i18n compatibility and accessibility, consider using:
- Standard ASCII "X"
- Regular emoji if branding is important
- Or just remove the symbol since the text already says "在 X 分享" (Share on X)
♻️ Proposed fix for better compatibility
- "laborHubJobsShareTweet": "𝕏 在 X 分享" + "laborHubJobsShareTweet": "X 在 X 分享"Or simply:
- "laborHubJobsShareTweet": "𝕏 在 X 分享" + "laborHubJobsShareTweet": "在 X 分享"🤖 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/messages/zh.json` at line 2242, The string value for the message key "laborHubJobsShareTweet" uses the mathematical bold capital X character (𝕏) which can render inconsistently; update the value to use a standard ASCII "X" (or an appropriate emoji or remove the symbol) so it reads e.g. "X 在 X 分享" or "在 X 分享" and ensure you modify the "laborHubJobsShareTweet" entry accordingly in the JSON.front_end/src/app/(main)/labor-hub/jobs/helpers/fetch_wage_and_hours.ts (1)
39-41: 💤 Low valueType assertion assumes post structure.
The type assertion
as QuestionWithNumericForecasts[] | undefinedassumes thegroup_of_questions.questionsstructure matches the expected type. If the runtime structure differs, this could cause errors ingetValueForLabel.Consider adding runtime validation or using a type guard to ensure type safety. However, if the post structure is well-known and validated elsewhere, the current approach is pragmatic.
🤖 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/app/`(main)/labor-hub/jobs/helpers/fetch_wage_and_hours.ts around lines 39 - 41, The code currently asserts post.group_of_questions?.questions as QuestionWithNumericForecasts[] | undefined which can hide runtime shape mismatches; add a runtime type guard (e.g., isQuestionWithNumericForecastsArray) and use it to validate post.group_of_questions?.questions before treating it as QuestionWithNumericForecasts[] (or fall back to undefined/empty array), then only call getValueForLabel with validated questions; reference the variables/values questions, post.group_of_questions?.questions and the helper getValueForLabel when locating where to add the check.front_end/src/app/(main)/labor-hub/jobs/helpers/fetch_job_insights.ts (1)
42-70: 💤 Low valueConsider more robust HTML tag stripping.
The regex
/<\/?[^>]+>/gon Line 59 won't match malformed tags like<script(without closing>). While React's default escaping mitigates XSS risk, using a proper HTML parser or a more defensive approach would be more robust for security-sensitive contexts.For the current use case (extracting plain text for display in React components), the risk is low.
🤖 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/app/`(main)/labor-hub/jobs/helpers/fetch_job_insights.ts around lines 42 - 70, stripBody's HTML stripping uses the fragile regex /<\/?[^>]+>/g which can miss malformed tags like "<script " — replace that step with a proper HTML-to-text approach: in the browser use DOMParser or create a temporary element (e.g., document.createElement and element.innerHTML = s; textContent) to reliably remove tags and handle malformed markup, and provide a safe fallback for non-browser environments (e.g., a more defensive regex that strips any "<" to next ">" or trims trailing "<" fragments). Update the stripBody function to call this parser-based sanitizer instead of the current regex so HTML tags (including malformed ones) are robustly removed before decoding entities and extracting the first paragraph.front_end/src/app/(main)/labor-hub/jobs/helpers/fetch_tile_tickers.ts (1)
11-42: ⚡ Quick winDuplicated sanitization logic across files.
The
decodeEntitiesandstripfunctions are duplicated fromfetch_job_insights.ts. Consider extracting these into a shared utility module to maintain consistency and reduce duplication.The same security considerations apply here: double-decoding and incomplete HTML sanitization are low-risk given React's default escaping, but could be improved for defense in depth.
🤖 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/app/`(main)/labor-hub/jobs/helpers/fetch_tile_tickers.ts around lines 11 - 42, Extract the duplicated decodeEntities and strip functions into a single shared utility module (e.g., sanitize or textUtils), export them, then replace the local implementations in this file by importing those exported functions; specifically remove the local decodeEntities and strip definitions and import decodeEntities and strip where they are used to ensure one canonical implementation (matching the existing implementation used in the other helper), run tests / lint and verify behavior is unchanged.
🤖 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/cs.json`:
- Line 2208: Update the value for the JSON key "laborHubJobsHeroLead" in
front_end/messages/cs.json by correcting the Czech typo: replace "Předpovědači"
with "Předpovídači" so the hero lead reads correctly; ensure you preserve the
rest of the string and surrounding punctuation/escaping.
In `@front_end/src/app/`(main)/labor-hub/jobs/components/exposure_metrics.tsx:
- Around line 117-131: Tooltip trigger is currently a non-focusable <span>,
making it inaccessible to keyboard users; replace it with a keyboard-focusable
<button type="button">. Locate the Tooltip usage (Tooltip component wrapping the
trigger) and replace the span element that has aria-label and className with a
<button type="button"> keeping the same aria-label, className, and child
FontAwesomeIcon; ensure no form submission by including type="button" and
preserve visual styling and tooltip props (tooltipContent, showDelayMs,
placement) so keyboard users can focus and activate the tooltip.
In `@front_end/src/app/`(main)/labor-hub/jobs/components/job_nav_strip.tsx:
- Around line 55-64: In job_nav_strip.tsx, the active Link (the job pill that
uses isActive and tone(item.value2035)) isn't exposed to assistive tech; add an
aria-current attribute to the Link element so screen readers announce the
current item (e.g., set aria-current="page" when isActive, otherwise omit or set
undefined) to the Link that points to `/labor-hub/jobs/${item.slug}/`.
In `@front_end/src/app/`(main)/labor-hub/jobs/components/jobs_wall.tsx:
- Around line 141-152: This control is a segmented toggle but currently uses
partial tab semantics; change the container from role="tablist" to role="group"
(or remove role) and on each button remove role="tab" and aria-selected and
instead add aria-pressed={y === year} so the buttons use toggle semantics; keep
the aria-label={t("laborHubJobsYearToggleLabel")} on the container and keep
onClick={() => setYear(y)} and key={y} as-is (references: WALL_YEARS, year,
setYear).
In `@front_end/src/app/`(main)/labor-hub/jobs/helpers/fetch_job_insights.ts:
- Around line 28-40: The decodeEntities function currently decodes numeric
entities then named entities which can double-decode sequences like "&lt;"
into "<"; update decodeEntities so named entities (&, <, >, ",
&`#39`;, ', ) are replaced first and numeric/hex replacements (&`#x`...;
and &#...;) are applied afterwards, or alternatively add a clear docstring on
decodeEntities stating it must only be used for plain-text contexts and must not
be used with dangerouslySetInnerHTML; modify the implementation in
decodeEntities accordingly to prevent unintended double-decoding.
In `@front_end/src/app/og/labor-hub/jobs/`[slug]/route/route.ts:
- Around line 17-18: Normalize the incoming year parameter by validating it
against the canonical set of allowed wall years before any use in URL
construction or download filename generation: introduce a constant list (e.g.,
knownWallYears) and replace direct use of req.nextUrl.searchParams.get("year")
(the local variable year) with a normalized value that is coerced to a string
from the allowed list (falling back to "2035" or the closest valid year) and use
that normalizedYear for all subsequent URL and filename building (including the
download flag handling and wherever the year is interpolated later in this
module).
- Around line 37-44: The POST to screenshotEndpoint using fetch (the const r =
await fetch(...) call) has no timeout; wrap the request with an AbortController,
pass controller.signal into fetch, and start a setTimeout that calls
controller.abort() after a configured timeout (e.g., SCREENSHOT_SERVICE_TIMEOUT
or a sensible default). Clear the timeout on successful response, and handle the
abort case by catching the thrown error and returning/throwing a clear
timeout/error response from this route handler so upstream slowness cannot hang
the request.
- Around line 23-26: Guard the construction of screenshotEndpoint by validating
process.env.SCREENSHOT_SERVICE_API_URL before calling new URL; specifically,
check that SCREENSHOT_SERVICE_API_URL is defined and is a valid URL (e.g.,
attempt to construct a URL inside the existing try/catch or validate with a
small helper) and only then build screenshotEndpoint, otherwise throw or return
a structured JSON error so the existing error handling catches malformed/missing
env values; update the code that creates screenshotEndpoint (the new URL(...)
call) to live inside that validation block and reference
SCREENSHOT_SERVICE_API_URL and screenshotEndpoint accordingly.
---
Nitpick comments:
In `@front_end/messages/zh.json`:
- Line 2242: The string value for the message key "laborHubJobsShareTweet" uses
the mathematical bold capital X character (𝕏) which can render inconsistently;
update the value to use a standard ASCII "X" (or an appropriate emoji or remove
the symbol) so it reads e.g. "X 在 X 分享" or "在 X 分享" and ensure you modify the
"laborHubJobsShareTweet" entry accordingly in the JSON.
In `@front_end/src/app/`(main)/labor-hub/jobs/helpers/fetch_job_insights.ts:
- Around line 42-70: stripBody's HTML stripping uses the fragile regex
/<\/?[^>]+>/g which can miss malformed tags like "<script " — replace that step
with a proper HTML-to-text approach: in the browser use DOMParser or create a
temporary element (e.g., document.createElement and element.innerHTML = s;
textContent) to reliably remove tags and handle malformed markup, and provide a
safe fallback for non-browser environments (e.g., a more defensive regex that
strips any "<" to next ">" or trims trailing "<" fragments). Update the
stripBody function to call this parser-based sanitizer instead of the current
regex so HTML tags (including malformed ones) are robustly removed before
decoding entities and extracting the first paragraph.
In `@front_end/src/app/`(main)/labor-hub/jobs/helpers/fetch_tile_tickers.ts:
- Around line 11-42: Extract the duplicated decodeEntities and strip functions
into a single shared utility module (e.g., sanitize or textUtils), export them,
then replace the local implementations in this file by importing those exported
functions; specifically remove the local decodeEntities and strip definitions
and import decodeEntities and strip where they are used to ensure one canonical
implementation (matching the existing implementation used in the other helper),
run tests / lint and verify behavior is unchanged.
In `@front_end/src/app/`(main)/labor-hub/jobs/helpers/fetch_wage_and_hours.ts:
- Around line 39-41: The code currently asserts
post.group_of_questions?.questions as QuestionWithNumericForecasts[] | undefined
which can hide runtime shape mismatches; add a runtime type guard (e.g.,
isQuestionWithNumericForecastsArray) and use it to validate
post.group_of_questions?.questions before treating it as
QuestionWithNumericForecasts[] (or fall back to undefined/empty array), then
only call getValueForLabel with validated questions; reference the
variables/values questions, post.group_of_questions?.questions and the helper
getValueForLabel when locating where to add the check.
🪄 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: d8ca2956-cc8c-40d2-afd1-7fc7b588a830
📒 Files selected for processing (32)
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)/labor-hub/data.tsfront_end/src/app/(main)/labor-hub/jobs/[slug]/page.tsxfront_end/src/app/(main)/labor-hub/jobs/components/bento_layout.tsxfront_end/src/app/(main)/labor-hub/jobs/components/curated_insights.tsxfront_end/src/app/(main)/labor-hub/jobs/components/exposure_metrics.tsxfront_end/src/app/(main)/labor-hub/jobs/components/hub_cta_card.tsxfront_end/src/app/(main)/labor-hub/jobs/components/job_nav_strip.tsxfront_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsxfront_end/src/app/(main)/labor-hub/jobs/components/share_card.tsxfront_end/src/app/(main)/labor-hub/jobs/components/share_card_preview.tsxfront_end/src/app/(main)/labor-hub/jobs/components/wage_hours_cards.tsxfront_end/src/app/(main)/labor-hub/jobs/components/year_stats.tsxfront_end/src/app/(main)/labor-hub/jobs/helpers/build_comment_url.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/exposure_thresholds.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/fetch_job_insights.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/fetch_tile_tickers.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/fetch_wage_and_hours.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/fetch_wall_data.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/wall_types.tsfront_end/src/app/(main)/labor-hub/jobs/page.tsxfront_end/src/app/og/labor-hub/jobs/[slug]/page.tsxfront_end/src/app/og/labor-hub/jobs/[slug]/route/route.tsfront_end/src/components/gradient-carousel.tsxfront_end/src/constants/colors.tsfront_end/src/utils/fonts.tsfront_end/tailwind.config.ts
🚀 Preview EnvironmentYour preview environment is ready!
Details
ℹ️ Preview Environment InfoIsolation:
Limitations:
Cleanup:
|
…ity fixes Round 3 of the consumer-friendly job pages. Layout & visuals - Job Detail hero and Jump-To strip share one container with a horizontal divider, matching the prototype - All Jobs hero and tile wall now share one container with a divider too - "survive AI?" in the hero h1 picks up the prototype's blue-600 accent via t.rich - Mobile breadcrumb drops the 3rd crumb (job name) so it doesn't wrap - Mobile wall is now a 3-col uniform grid, no size variation, no tickers - Bento on mobile: 5-tile grid (Felten, MNA, AOE, Wage, Hours); Curated Insights moves to its own full-width section below - Wage / Hours cards center contents on md+, left-align on mobile, consistent fonts across all 5 tiles - Share card preview switches to container queries (cqw units) so it scales proportionally on narrow viewports instead of squeezing - HIGH chip text in dark mode bumped to salmon-900-dark for readability - Chart card height reduced ~32px Jump-To strip - Prototype-matched chip style: uniform blue-100 / blue-900 active, no per-job color - Mobile: arrows + "Jump to:" label hidden, gradient fade tightened - Scroll position persisted across visits via sessionStorage; active pill is auto-scrolled into view via requestAnimationFrame after restore - aria-current on the active pill; data-active-pill attribute for the ensure-visible lookup - Arrows use items-center + w-9 h-9 so the FontAwesome icon centers correctly; light/dark bg flips via blue-900 / blue-900-dark - gradient-carousel: new optional gradientWidthClass prop (defaults to current w-[152px]); arrow snap-to-edge in scrollByAmount so clicking prev near scrollLeft 0 lands exactly at 0 (canPrev threshold bumped to > 1 for sub-pixel forgiveness) Chart - Reverted to MultiQuestionLineChart for typography + hover behavior - New historicalLabelText / forecastLabelText props on the underlying chart let the section labels be overridden; Job Detail uses "BASELINE" - Line + scatter color picked from 2035 direction (mc2/mc3/mc1) via getSeriesOptions - MultiQuestionLineChartSkeleton accepts the same height prop (and a showTitlePlaceholder flag) so the loading state matches the rendered size exactly — no page jump on hydration A11y & reliability (from the GHAS / CodeQL pass) - Tooltip trigger is now a <button type="button"> with a visible focus ring instead of a non-focusable <span> - Year toggle is role="group" + aria-pressed (was tablist/aria-selected with no associated panels) - decodeEntities (insights + tickers) is now a single-pass replacement so "&lt;" decodes once to "<" instead of double-decoding to "<"; docstring notes plain-text-only usage - OG route /og/labor-hub/jobs/[slug]/route hardened: ?year= clamped to WALL_YEARS allowlist, SCREENSHOT_SERVICE_API_URL validated before use (structured 500 on missing/invalid), fetch wrapped in an AbortController with a 15-second timeout (504 on abort) - Czech translation fix: "Předpovědač" → "Předpovídač" across the three cs.json strings that contained it Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
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/app/og/labor-hub/jobs/[slug]/route/route.ts (2)
71-74:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAvoid returning raw upstream error bodies to clients.
Passing upstream text through directly can leak internal service details. Return a stable generic error payload instead.
Proposed fix
if (!r.ok) { - const text = await r.text(); - return NextResponse.json({ error: text }, { status: r.status }); + return NextResponse.json( + { error: "screenshot service failed" }, + { status: r.status >= 400 && r.status < 600 ? r.status : 502 } + ); }🤖 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/app/og/labor-hub/jobs/`[slug]/route/route.ts around lines 71 - 74, The handler currently returns raw upstream response bodies (using r.text()) which can leak internal details; instead, read the upstream body for server-side logging (e.g., console.error or your logger) and return a fixed, generic JSON error payload to the client via NextResponse.json while preserving the HTTP status (use r.status). Update the branch that checks r.ok to stop forwarding raw text, log the detailed text internally, and return a stable message such as { error: "Upstream service error" } with NextResponse.json and status r.status.
62-65:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFail fast when the screenshot API key is missing.
This currently sends
api_key: ""upstream, which turns misconfiguration into avoidable external failures instead of a clear local 500.Proposed fix
+ const apiKey = process.env.SCREENSHOT_SERVICE_API_KEY; + if (!apiKey) { + return NextResponse.json( + { error: "screenshot service API key not configured" }, + { status: 500 } + ); + } + try { const r = await fetch(screenshotEndpoint, { method: "POST", headers: { "Content-Type": "application/json", - api_key: process.env.SCREENSHOT_SERVICE_API_KEY || "", + api_key: apiKey, },🤖 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/app/og/labor-hub/jobs/`[slug]/route/route.ts around lines 62 - 65, The code currently sends api_key: "" when process.env.SCREENSHOT_SERVICE_API_KEY is missing; update the route handler (the GET request handler in route.ts) to validate process.env.SCREENSHOT_SERVICE_API_KEY before making the upstream call and fail fast with a 500/explicit error if it's falsy. Specifically, check SCREENSHOT_SERVICE_API_KEY at the start of the handler and return an error response (or throw) instead of continuing; then build the headers object with api_key set to the validated value so you never send an empty string upstream.
🤖 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.
Outside diff comments:
In `@front_end/src/app/og/labor-hub/jobs/`[slug]/route/route.ts:
- Around line 71-74: The handler currently returns raw upstream response bodies
(using r.text()) which can leak internal details; instead, read the upstream
body for server-side logging (e.g., console.error or your logger) and return a
fixed, generic JSON error payload to the client via NextResponse.json while
preserving the HTTP status (use r.status). Update the branch that checks r.ok to
stop forwarding raw text, log the detailed text internally, and return a stable
message such as { error: "Upstream service error" } with NextResponse.json and
status r.status.
- Around line 62-65: The code currently sends api_key: "" when
process.env.SCREENSHOT_SERVICE_API_KEY is missing; update the route handler (the
GET request handler in route.ts) to validate
process.env.SCREENSHOT_SERVICE_API_KEY before making the upstream call and fail
fast with a 500/explicit error if it's falsy. Specifically, check
SCREENSHOT_SERVICE_API_KEY at the start of the handler and return an error
response (or throw) instead of continuing; then build the headers object with
api_key set to the validated value so you never send an empty string upstream.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 65939d16-41d4-4d9c-ac1f-d6e56d0f4653
📒 Files selected for processing (20)
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)/labor-hub/components/question_cards/multi_line_chart.tsxfront_end/src/app/(main)/labor-hub/components/question_cards/multi_question_line_chart.tsxfront_end/src/app/(main)/labor-hub/jobs/[slug]/page.tsxfront_end/src/app/(main)/labor-hub/jobs/components/bento_layout.tsxfront_end/src/app/(main)/labor-hub/jobs/components/exposure_metrics.tsxfront_end/src/app/(main)/labor-hub/jobs/components/job_nav_strip.tsxfront_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsxfront_end/src/app/(main)/labor-hub/jobs/components/share_card_preview.tsxfront_end/src/app/(main)/labor-hub/jobs/components/wage_hours_cards.tsxfront_end/src/app/(main)/labor-hub/jobs/helpers/fetch_job_insights.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/fetch_tile_tickers.tsfront_end/src/app/(main)/labor-hub/jobs/page.tsxfront_end/src/app/og/labor-hub/jobs/[slug]/route/route.tsfront_end/src/components/gradient-carousel.tsx
✅ Files skipped from review due to trivial changes (2)
- front_end/messages/pt.json
- front_end/messages/es.json
CodeQL flagged "incomplete multi-character sanitization" on the `/<\/?[^>]+>/` tag-stripper in fetch_job_insights.ts and fetch_tile_tickers.ts: a single pass can be defeated (e.g. `<scr<b>ipt>` re-forms `<script>`), and decodeEntities ran afterward so `<script>` decoded into `<script>` with nothing left to strip it. Drop the incomplete tag regex; decode entities first, then remove every angle bracket so no HTML tag syntax can survive or re-form. The output is rendered as plain text (React-escaped), so removing `<`/`>` is safe and constitutes complete sanitization. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ing, scroll retention, share-card polish Below Jump-To - Drop the Hours tile; keep Wage only when the job has wage data (wage_post_id) — it sits above the 3 exposure tiles in the right rail - New WageTile + trimmed fetch_wage helper; remove wage_hours_cards / fetch_wage_and_hours - BentoLayout is one reflowing grid: desktop = Insights (2/3) + data rail (1/3); mobile = data rail then Insights in one container. Exposure tiles are 3-across on mobile, stacked on desktop Jump-To strip - Chip % numbers are bigger, bold, colored green/red by sign; inactive chips use blue-200 - Horizontal scroll now retained across job switches via an in-memory module variable + double-rAF restore; only a fresh visit auto-centers the active pill Forecast chart (hero + share card) - New opt-in colorByValue on the shared MultiLineChart: baseline gray, positive green, negative red, with a horizontal gradient blending mixed-sign segments; per-point dot colors and per-point hover/data-label badge colors - yAxisGutter widened so two-digit negative labels show their minus sign; chart card fills the hero row height and centers the chart - Tickers: animation delay folded into the shorthand (fixes the shorthand/longhand React warning); dedup so the same comment doesn't slide across many tiles Share card - Self-contained SVG with the per-value-colored forecast line + dashed gridlines behind the number; green dot removed; thicker/bolder line - Client-side PNG export (SVG→canvas), no screenshot-service dependency; year toggle removed (always 2035) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/app/`(main)/labor-hub/components/question_cards/multi_line_chart.tsx:
- Around line 747-761: The gradient id generation in MultiLineChart currently
builds ValueGradient.id using only item.id, which can collide across multiple
instances; update the component that renders the chart (MultiLineChart) to
create a stable per-instance prefix (e.g., from React's useId() or a generated
stableId) and prepend it to the gradient id (replace
`lh-value-grad-${item.id...}` with something like
`{instanceId}-lh-value-grad-${item.id...}`) and ensure the same composed id is
used wherever the gradient is referenced (e.g., in stroke: url(#...)) so each
chart instance has unique SVG ids.
In `@front_end/src/app/`(main)/labor-hub/jobs/components/bento_layout.tsx:
- Around line 8-19: In BentoLayout, the mobile ordering is wrong: mobile should
render dataRail before insights but currently renders {insights} first because
the Tailwind order classes only apply from md up; fix by changing the element
order or adding mobile order classes so that dataRail appears first on small
screens (e.g., ensure the container renders dataRail before insights or apply
classes like order-1/order-2 on the divs containing dataRail and insights
respectively while keeping md:order-* for desktop), targeting the JSX nodes that
render {insights} and {dataRail} in the BentoLayout function.
🪄 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: 03598874-b8d1-404a-be51-50f73e82fb38
📒 Files selected for processing (22)
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)/labor-hub/components/question_cards/multi_line_chart.tsxfront_end/src/app/(main)/labor-hub/components/question_cards/multi_line_chart.types.tsfront_end/src/app/(main)/labor-hub/components/question_cards/multi_question_line_chart.tsxfront_end/src/app/(main)/labor-hub/data.tsfront_end/src/app/(main)/labor-hub/jobs/[slug]/page.tsxfront_end/src/app/(main)/labor-hub/jobs/components/bento_layout.tsxfront_end/src/app/(main)/labor-hub/jobs/components/exposure_metrics.tsxfront_end/src/app/(main)/labor-hub/jobs/components/job_nav_strip.tsxfront_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsxfront_end/src/app/(main)/labor-hub/jobs/components/share_card.tsxfront_end/src/app/(main)/labor-hub/jobs/components/share_card_preview.tsxfront_end/src/app/(main)/labor-hub/jobs/components/wage_tile.tsxfront_end/src/app/(main)/labor-hub/jobs/helpers/fetch_job_insights.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/fetch_tile_tickers.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/fetch_wage.tsfront_end/src/app/og/labor-hub/jobs/[slug]/page.tsx
💤 Files with no reviewable changes (1)
- front_end/src/app/(main)/labor-hub/data.ts
✅ Files skipped from review due to trivial changes (2)
- front_end/messages/pt.json
- front_end/messages/es.json
🚧 Files skipped from review as they are similar to previous changes (8)
- front_end/messages/en.json
- front_end/src/app/(main)/labor-hub/jobs/helpers/fetch_tile_tickers.ts
- front_end/src/app/(main)/labor-hub/components/question_cards/multi_question_line_chart.tsx
- front_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsx
- front_end/messages/zh.json
- front_end/messages/cs.json
- front_end/messages/zh-TW.json
- front_end/src/app/(main)/labor-hub/jobs/helpers/fetch_job_insights.ts
# Conflicts: # front_end/messages/cs.json # front_end/messages/es.json # front_end/messages/pt.json # front_end/messages/zh-TW.json # front_end/messages/zh.json
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- zh-TW feedTileQuestionsRecentlyResolved: 解答 -> 解決 (match resolved terminology) - BaseModal: drop panel h-svh at the sm breakpoint (640-767px) where it clipped against the wrapper's sm:p-4; constrain to sm:max-h and scroll Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
front_end/src/app/(main)/labor-hub/jobs/helpers/metric_defs.ts (2)
21-67: ⚡ Quick winConsolidate metric value formatting to one source of truth.
Line 25 / Line 40 / Line 55 define formatting here, while
ExposureMetricsrenders values via a separateformatValue(...)path. This split can drift and produce conflicting numeric displays across tiles vs overlay/axis. Prefer routing both surfaces through the same formatter contract.🤖 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/app/`(main)/labor-hub/jobs/helpers/metric_defs.ts around lines 21 - 67, The per-metric inline format functions in METRIC_DEFS (the format properties on keys "felten", "mna", "aoe") are diverging from the rendering path used by ExposureMetrics/formatValue; unify them by moving formatting to a single source-of-truth and wiring both consumers to it—either export a shared formatter factory or replace the format properties with references to the central formatValue implementation and update ExposureMetrics to call METRIC_DEFS[metricKey].format (or vice versa) so tiles, overlays and axes all use the exact same formatting logic.
69-70: ⚡ Quick winUse the shared palette token instead of a hardcoded accent hex.
Line 70 hardcodes
#f87248; this should come from the shared color token (salmon-900) to prevent style drift and keep theming centralized.🤖 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/app/`(main)/labor-hub/jobs/helpers/metric_defs.ts around lines 69 - 70, Replace the hardcoded hex in the METRIC_ACCENT_WARM export with the shared palette token `salmon-900`: update the `export const METRIC_ACCENT_WARM = "`#f87248`";` line to reference the centralized color token (importing it if necessary) so the accent uses the shared `salmon-900` value instead of a literal hex.
🤖 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.
Nitpick comments:
In `@front_end/src/app/`(main)/labor-hub/jobs/helpers/metric_defs.ts:
- Around line 21-67: The per-metric inline format functions in METRIC_DEFS (the
format properties on keys "felten", "mna", "aoe") are diverging from the
rendering path used by ExposureMetrics/formatValue; unify them by moving
formatting to a single source-of-truth and wiring both consumers to it—either
export a shared formatter factory or replace the format properties with
references to the central formatValue implementation and update ExposureMetrics
to call METRIC_DEFS[metricKey].format (or vice versa) so tiles, overlays and
axes all use the exact same formatting logic.
- Around line 69-70: Replace the hardcoded hex in the METRIC_ACCENT_WARM export
with the shared palette token `salmon-900`: update the `export const
METRIC_ACCENT_WARM = "`#f87248`";` line to reference the centralized color token
(importing it if necessary) so the accent uses the shared `salmon-900` value
instead of a literal hex.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 762c2d7d-7eca-46fe-b0bf-caf3dafdae65
📒 Files selected for processing (23)
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)/labor-hub/components/question_cards/multi_line_chart.tsxfront_end/src/app/(main)/labor-hub/components/question_cards/multi_question_line_chart.tsxfront_end/src/app/(main)/labor-hub/data.tsfront_end/src/app/(main)/labor-hub/jobs/[slug]/page.tsxfront_end/src/app/(main)/labor-hub/jobs/components/bento_layout.tsxfront_end/src/app/(main)/labor-hub/jobs/components/exposure_metrics.tsxfront_end/src/app/(main)/labor-hub/jobs/components/job_nav_strip.tsxfront_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsxfront_end/src/app/(main)/labor-hub/jobs/components/metric_comparison_axis.tsxfront_end/src/app/(main)/labor-hub/jobs/components/metric_overlay.tsxfront_end/src/app/(main)/labor-hub/jobs/components/share_card.tsxfront_end/src/app/(main)/labor-hub/jobs/components/year_stats.tsxfront_end/src/app/(main)/labor-hub/jobs/data/oews_history.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/format.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/metric_defs.tsfront_end/src/app/(main)/labor-hub/sections/jobs_monitor.tsxfront_end/src/components/base_modal.tsx
✅ Files skipped from review due to trivial changes (1)
- front_end/src/app/(main)/labor-hub/jobs/helpers/format.ts
🚧 Files skipped from review as they are similar to previous changes (19)
- front_end/src/app/(main)/labor-hub/jobs/components/year_stats.tsx
- front_end/src/app/(main)/labor-hub/sections/jobs_monitor.tsx
- front_end/src/app/(main)/labor-hub/jobs/components/exposure_metrics.tsx
- front_end/src/components/base_modal.tsx
- front_end/src/app/(main)/labor-hub/jobs/components/job_nav_strip.tsx
- front_end/src/app/(main)/labor-hub/data.ts
- front_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsx
- front_end/src/app/(main)/labor-hub/jobs/components/share_card.tsx
- front_end/messages/en.json
- front_end/src/app/(main)/labor-hub/jobs/[slug]/page.tsx
- front_end/src/app/(main)/labor-hub/jobs/components/bento_layout.tsx
- front_end/src/app/(main)/labor-hub/components/question_cards/multi_question_line_chart.tsx
- front_end/src/app/(main)/labor-hub/jobs/data/oews_history.ts
- front_end/messages/es.json
- front_end/messages/zh.json
- front_end/messages/zh-TW.json
- front_end/messages/cs.json
- front_end/src/app/(main)/labor-hub/components/question_cards/multi_line_chart.tsx
- front_end/messages/pt.json
…ponsive fits - Jobs Monitor: add a primary "Visit Jobs" button (right arrow) left of the "..." menu via a new optional QuestionCard headerActions slot; links to /labor-hub/jobs/. New i18n key laborHubJobsVisitCta x6. - Jump-To strip: rewrite scroll handling — drop the retained-scroll/rAF logic and center the active chip instantly in an isomorphic layout effect, so it's always visible and pre-positioned with no post-load animation. - Exposure overlay axis line: fix near-black-in-light (bg-blue-700 dark: bg-blue-950-dark); add a faint cursor-following blurred glow clipped to the line. - Responsive fits: smaller wall tile numbers on narrow screens; smaller job-detail BY-year stat numbers; stack exposure tile label over chip on mobile. - All Jobs page: float the tabs/subtitle/tiles in a transparent container below the hero box (no divider), wider on mobile. - Jump-To: shrink mobile edge gradient 60px -> 15px. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/app/`(main)/labor-hub/sections/jobs_monitor.tsx:
- Around line 117-127: The Jobs Monitor currently includes Buttons/links with
href="/labor-hub/jobs/" (e.g., the headerActions Button in jobs_monitor.tsx and
the other CTAs that reference /labor-hub/jobs/*); remove the navigation by
deleting the href attributes (or converting the anchor-style Button into a
non-link Button) so they are non-navigable in this PR, and optionally add
disabled or remove onClick handlers to keep the visual CTA but prevent
navigation; ensure you update every occurrence that references
"/labor-hub/jobs/" (including the headerActions Button and the other CTAs around
the noted blocks) so no element in this component links to /labor-hub/jobs/ yet.
🪄 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: 233ebf8d-2013-427e-b3e1-28c8638a17c9
📒 Files selected for processing (25)
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)/labor-hub/components/question_cards/multi_line_chart.tsxfront_end/src/app/(main)/labor-hub/components/question_cards/multi_question_line_chart.tsxfront_end/src/app/(main)/labor-hub/components/question_cards/question_card.tsxfront_end/src/app/(main)/labor-hub/data.tsfront_end/src/app/(main)/labor-hub/jobs/[slug]/page.tsxfront_end/src/app/(main)/labor-hub/jobs/components/bento_layout.tsxfront_end/src/app/(main)/labor-hub/jobs/components/exposure_metrics.tsxfront_end/src/app/(main)/labor-hub/jobs/components/job_nav_strip.tsxfront_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsxfront_end/src/app/(main)/labor-hub/jobs/components/metric_comparison_axis.tsxfront_end/src/app/(main)/labor-hub/jobs/components/metric_overlay.tsxfront_end/src/app/(main)/labor-hub/jobs/components/share_card.tsxfront_end/src/app/(main)/labor-hub/jobs/components/year_stats.tsxfront_end/src/app/(main)/labor-hub/jobs/data/oews_history.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/format.tsfront_end/src/app/(main)/labor-hub/jobs/helpers/metric_defs.tsfront_end/src/app/(main)/labor-hub/jobs/page.tsxfront_end/src/app/(main)/labor-hub/sections/jobs_monitor.tsxfront_end/src/components/base_modal.tsx
🚧 Files skipped from review as they are similar to previous changes (21)
- front_end/src/app/(main)/labor-hub/jobs/helpers/format.ts
- front_end/src/app/(main)/labor-hub/jobs/components/bento_layout.tsx
- front_end/src/app/(main)/labor-hub/jobs/helpers/metric_defs.ts
- front_end/src/app/(main)/labor-hub/jobs/components/metric_overlay.tsx
- front_end/src/app/(main)/labor-hub/jobs/components/exposure_metrics.tsx
- front_end/src/app/(main)/labor-hub/jobs/page.tsx
- front_end/src/components/base_modal.tsx
- front_end/src/app/(main)/labor-hub/jobs/components/share_card.tsx
- front_end/src/app/(main)/labor-hub/jobs/data/oews_history.ts
- front_end/src/app/(main)/labor-hub/data.ts
- front_end/src/app/(main)/labor-hub/components/question_cards/multi_question_line_chart.tsx
- front_end/src/app/(main)/labor-hub/jobs/components/year_stats.tsx
- front_end/messages/pt.json
- front_end/messages/zh-TW.json
- front_end/src/app/(main)/labor-hub/jobs/[slug]/page.tsx
- front_end/messages/en.json
- front_end/messages/es.json
- front_end/messages/cs.json
- front_end/messages/zh.json
- front_end/src/app/(main)/labor-hub/components/question_cards/multi_line_chart.tsx
- front_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsx
- Vertically center the axis dots on the thicker background line - Split the cursor glow into independent light/dark styles - Scope the dot color change to the hovered dot only (was darkening every dot on axis hover); dark mode now lightens the hovered dot (blue-700-dark) - Plus manual styling tweaks to the Labor Hub jobs pages Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # front_end/messages/cs.json # front_end/messages/en.json # front_end/messages/es.json # front_end/messages/pt.json # front_end/messages/zh-TW.json # front_end/messages/zh.json
- Rewrite the 2nd/3rd exposure-metric copy (hallucinated attributions/scales): 2nd is now Manning & Aguirre "M&A vulnerability" (0–1, 3dp); 3rd is Massenkoff & McCrory "Anthropic Exposure" / AOE (0–100%). Felten gets the itemized title/source/bullet/box fixes. - Remove the middle bullet from all three overlays (natureKeys -> [n1, n3]). - Number formats: mna -> v.toFixed(3); aoe -> percentage (tile + overlay + axis). - All copy updated across the 6 locale files; orphaned …Nature2 keys left in place. - Year toggle: white background in light mode (matches dark mode). - Invert the "Explore the full Labor Hub" CTA (dark in light / light in dark). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/es.json`:
- Line 2346: The string key laborHubJobsMnaNature2 in es.json currently
describes a "proporción… (0–100 %)" and uses AOE-like wording; update its
Spanish text to describe the MNA vulnerability score on a 0–1 scale (consistent
with laborHubJobsMnaAxisLabel "0–1" and laborHubJobsMnaBounds "0 a 1") and
rephrase the semantics to match vulnerability (e.g., higher values = greater
vulnerability due to exposure and low adaptive capacity) instead of a percentage
or AOE framing so the wording aligns with laborHubJobsMnaNature3 and the defined
bounds.
In `@front_end/messages/pt.json`:
- Line 2343: The string for laborHubJobsMnaNature2 is incorrect: it currently
describes task-share/AI usage (AOE) but should describe the Manning & Aguirre
(MNA) vulnerability score. Update the value for the key "laborHubJobsMnaNature2"
to a concise Portuguese description that MNA is a 0–1 vulnerability measure
combining AI exposure with worker adaptability (e.g., "MNA é a pontuação de
vulnerabilidade (0–1) que combina a exposição à IA com a adaptabilidade dos
trabalhadores"), ensuring it matches the axis label and bounds for MNA.
🪄 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: 788e5249-db1b-4094-8b60-afbb0593ec14
📒 Files selected for processing (10)
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)/labor-hub/jobs/components/exposure_metrics.tsxfront_end/src/app/(main)/labor-hub/jobs/components/hub_cta_card.tsxfront_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsxfront_end/src/app/(main)/labor-hub/jobs/helpers/metric_defs.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- front_end/src/app/(main)/labor-hub/jobs/components/hub_cta_card.tsx
- front_end/messages/zh-TW.json
- front_end/messages/en.json
- front_end/src/app/(main)/labor-hub/jobs/components/jobs_wall.tsx
- front_end/messages/zh.json
- front_end/messages/cs.json
| "laborHubJobsMnaAxisLabel": "Vulnerabilidad de Manning & Aguirre (0–1)", | ||
| "laborHubJobsMnaNature1": "Medida académica que combina la exposición a la IA con la capacidad de adaptación de los trabajadores.", | ||
| "laborHubJobsMnaNature2": "El valor es la <strong>proporción de las tareas de esta ocupación (0–100 %)</strong> que aparecen en el uso medido de la IA.", | ||
| "laborHubJobsMnaNature3": "Las puntuaciones de vulnerabilidad se relacionan tanto con la probabilidad de desplazamiento (a través de la exposición a la IA) como con su carga (a través de la capacidad de adaptación).", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if laborHubJobsMnaNature2 is referenced in the codebase
rg -n 'laborHubJobsMnaNature2' --type ts --type tsx --type js --type jsxRepository: Metaculus/metaculus
Length of output: 91
Fix inconsistent MNA (Manning & Aguirre) Nature2 description
laborHubJobsMnaNature2 describes a “proporción… (0–100 %)”, but the MNA metric is defined with a 0–1 scale (laborHubJobsMnaAxisLabel “0–1”, laborHubJobsMnaBounds “0 a 1”). The text also reads like the AOE nature2 semantics rather than an MNA vulnerability score—update laborHubJobsMnaNature2 to match the 0–1 vulnerability meaning to avoid confusion.
🤖 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/messages/es.json` at line 2346, The string key
laborHubJobsMnaNature2 in es.json currently describes a "proporción… (0–100 %)"
and uses AOE-like wording; update its Spanish text to describe the MNA
vulnerability score on a 0–1 scale (consistent with laborHubJobsMnaAxisLabel
"0–1" and laborHubJobsMnaBounds "0 a 1") and rephrase the semantics to match
vulnerability (e.g., higher values = greater vulnerability due to exposure and
low adaptive capacity) instead of a percentage or AOE framing so the wording
aligns with laborHubJobsMnaNature3 and the defined bounds.
| "laborHubJobsMnaSource": "Manning & Aguirre — Pontuação de vulnerabilidade", | ||
| "laborHubJobsMnaAxisLabel": "Vulnerabilidade de Manning & Aguirre (0–1)", | ||
| "laborHubJobsMnaNature1": "Medida acadêmica que combina a exposição à IA com a capacidade de adaptação dos trabalhadores.", | ||
| "laborHubJobsMnaNature2": "O valor é a <strong>parcela das tarefas desta ocupação (0–100%)</strong> que aparecem no uso medido de IA.", |
There was a problem hiding this comment.
Incorrect metric description for Manning & Aguirre (MNA).
Line 2343 describes MNA as "the share of this occupation's tasks (0–100%) that appear in measured AI usage," but this is the definition of AOE (Anthropic Occupational Exposure), not MNA.
According to the PR objectives, MNA is the "Manning & Aguirre vulnerability" score (0–1 scale), which combines AI exposure with worker adaptability. The description at line 2343 appears to have been copied from the AOE metric description (line 2349).
The axis label (line 2341) and bounds (line 2345) correctly state MNA is 0–1, but the Nature2 description contradicts this by describing task percentages and AI usage—characteristics of the AOE metric, not MNA.
📝 Suggested correction
Replace line 2343 with a description that accurately reflects MNA's definition as a vulnerability measure combining exposure and adaptability, consistent with the other MNA strings:
- "laborHubJobsMnaNature2": "O valor é a <strong>parcela das tarefas desta ocupação (0–100%)</strong> que aparecem no uso medido de IA.",
+ "laborHubJobsMnaNature2": "É uma <strong>medida de 0 a 1</strong> — valores mais altos indicam maior vulnerabilidade à automação, considerando tanto a exposição à IA quanto a capacidade de adaptação dos trabalhadores.",📝 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.
| "laborHubJobsMnaNature2": "O valor é a <strong>parcela das tarefas desta ocupação (0–100%)</strong> que aparecem no uso medido de IA.", | |
| "laborHubJobsMnaNature2": "É uma <strong>medida de 0 a 1</strong> — valores mais altos indicam maior vulnerabilidade à automação, considerando tanto a exposição à IA quanto a capacidade de adaptação dos trabalhadores.", |
🤖 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/messages/pt.json` at line 2343, The string for
laborHubJobsMnaNature2 is incorrect: it currently describes task-share/AI usage
(AOE) but should describe the Manning & Aguirre (MNA) vulnerability score.
Update the value for the key "laborHubJobsMnaNature2" to a concise Portuguese
description that MNA is a 0–1 vulnerability measure combining AI exposure with
worker adaptability (e.g., "MNA é a pontuação de vulnerabilidade (0–1) que
combina a exposição à IA com a adaptabilidade dos trabalhadores"), ensuring it
matches the axis label and bounds for MNA.
Summary
Adds two consumer-facing routes under the Labor Hub as SEO-friendly entry points to the same forecast data, derived from the Claude Design "Fast Food" prototype:
/labor-hub/jobs/— Forecast Wall: a sized-by-impact tile grid of all 15 tracked occupations with a year toggle (2027 / 2030 / 2035) and a hover-only news-ticker preview on each tile/labor-hub/jobs/[slug]/— Job Detail page: breadcrumb hero with employment-forecast chart, Jump To carousel across all 15 jobs, Felten / MNA / AOE exposure metrics with HIGH/MED/LOW chips + tooltips, Curated Insights (data.ts override → top comments → keyword-matched comments fallback) with click-through to the source comment, Wages + economy-wide Hours bento, share card (1.91:1) with year toggle, Save Image PNG + Share on XData: 15 occupations get a
slugfield ondata.ts; optionalwage_post_id,curated_insights,keyword_aliases, andexcluded_comment_idsavailable per job for future curation without code changes.Reuse, no new packages:
MultiQuestionLineChartfor the hero chart,gradient-carouselfor Jump To,MobileCarouselfor the mobile bento, existingSCREENSHOT_SERVICE_API_URLfor the Share Card OG image. JetBrains Mono added vianext/font/google;salmon-900added to the palette.SEO: per-page
generateMetadata+generateStaticParams(15 prebuilt detail pages), OpenGraph + Twitter card, JSON-LD (Dataset per job, ItemList on All Jobs).i18n: ~30 new
laborHubJobs*keys translated into all 6 locales; job names left in English.The existing Labor Hub dashboard is untouched, and there is intentionally no link from
/labor-hub/to/labor-hub/jobs/in this first pass — discoverability will be handled separately.Test plan
cd front_end && bun run buildsucceeds,.next/server/app/(main)/labor-hub/jobs/page.jsand[slug]/page.jsare generated/labor-hub/jobs/renders the 15-tile wall; year toggle re-sorts sizes/labor-hub/jobs/software-developers/renders with Felten chip = HIGH, tooltip on?icon, Curated Insights populated, Wages + Hours bento, Share card with Save Image / Share on Xview-source:on either route shows<title>, meta description, OG tags, Twitter card, and<script type="application/ld+json">🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Localization
UI Improvements
Accessibility