feat(forecast): simulation confidence sub-bar in ForecastPanel#2526
feat(forecast): simulation confidence sub-bar in ForecastPanel#2526
Conversation
Add three fields to Forecast proto (simulation_adjustment, sim_path_confidence, demoted_by_simulation) and implement a thin colored underbar below each forecast title that encodes simulation evidence without adding columns or text clutter. Visual design (Option D): - 2px colored bar, width = sim path confidence for positive adj, 100% for negative/demoted (structural, not confidence-dependent) - Green ≥0.70 conf, amber <0.70, orange negative, red demoted - Opacity 0.45 at rest; 0.9 + text label on hover - Plain language hover labels: "AI signal · +8%", "AI caution · −12%", "AI flag: dropped · −15%" — no "sim" jargon visible to users - Demoted rows dim to opacity 0.5 Passes through simulation fields in buildPublishedForecastPayload. No chip renders until the ExpandedPath → Forecast plumbing lands (follow-up PR); all rendering code is ready and typecheck clean. 🤖 Generated with Claude Sonnet 4.6 via Claude Code + Compound Engineering v2.49.0 Co-Authored-By: Claude Sonnet 4.6 (200K context) <[email protected]>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Greptile SummaryThis PR adds three simulation-scoring proto fields ( Key changes:
Issues found:
Confidence Score: 5/5Safe to merge — the sub-bar is a no-op until the plumbing PR lands, and all three findings are P2 visual/style improvements All remaining findings are P2: the fallback default requires undefined (not zero) to misfire, the opacity stacking affects only the visual polish of demoted rows (which won't appear in production yet), and the layout-shift is a UX improvement suggestion. No P0 or P1 issues found. src/components/ForecastPanel.ts — three style/visual issues in renderSimBar and CSS rules worth fixing before the plumbing PR activates the feature Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[proto Forecast message\nfields 20-22 added] --> B[buf generate]
B --> C[service_client.ts\nForecast interface]
B --> D[service_server.ts\nForecast interface]
C --> E[ForecastPanel.ts\nrenderProbRow]
D --> F[seed-forecasts.mjs\nbuildPublishedForecastPayload]
F --> G[(Redis published\nforecast key)]
G -->|plumbing PR future| H[applyPostSimulationRescore\nwrites sim decorations]
H --> G
G --> I[GetForecasts API]
I --> E
E --> J{simulationAdjustment != 0?}
J -- Yes --> K[renderSimBar]
K --> L{demotedBySimulation?}
L -- Yes --> M[Red bar 100% AI flag: dropped]
L -- No --> N{adj > 0?}
N -- Yes conf>=0.70 --> O[Green bar conf*100% width]
N -- Yes conf<0.70 --> P[Amber bar conf*100% width]
N -- No --> Q[Orange bar 100% AI caution]
J -- No --> R[No bar rendered]
Reviews (1): Last reviewed commit: "feat(forecast): simulation confidence su..." | Re-trigger Greptile |
| const adj = f.simulationAdjustment ?? 0; | ||
| if (adj === 0) return ''; | ||
|
|
||
| const conf = f.simPathConfidence ?? 1.0; |
There was a problem hiding this comment.
simPathConfidence fallback conflicts with proto "0 = not set" semantics
The proto comment explicitly documents 0 = not set for sim_path_confidence. Using ?? 1.0 as the fallback means that if simPathConfidence arrives as undefined at runtime (e.g. a Forecast object constructed without this field, or data in-flight before the plumbing PR lands), the bar will render as a full-width green "AI signal" — the exact opposite of "not set."
The correct fallback is 0, which matches the proto sentinel and falls through to the amber/moderate branch with minimum bar width (20%):
| const conf = f.simPathConfidence ?? 1.0; | |
| const conf = f.simPathConfidence ?? 0; |
With proto3 wire encoding this ?? 0 is dead code for well-formed responses, but it makes the JS fallback safe for any Forecast object that omits the field.
| <div class="fc-prob-row"> | ||
| <span class="fc-prob-label" | ||
| style="border-left:2px solid ${catColor}47;padding-left:6px"> | ||
| <div class="fc-prob-row"${demoted ? ' style="opacity:0.5"' : ''}> |
There was a problem hiding this comment.
Demoted-row
opacity: 0.5 stacks with fc-sim-bar opacity, defeating hover intent
CSS opacity is applied multiplicatively through the DOM tree. Setting opacity: 0.5 on fc-prob-row (this line) means the fc-sim-bar inside it can only reach 0.5 × 0.9 = 0.45 effective opacity on hover — not the 0.9 the CSS rule targets. More critically, the .fc-sim-label text that is supposed to become fully legible on hover also inherits the 0.5 dimming, keeping it at half readability.
For demoted forecasts this is most important — the "AI flag: dropped" label is the signal users most need to read.
One approach is to apply the dimming to the title text only (rather than the whole row), leaving the sim bar and label at full stacking opacity:
| <div class="fc-prob-row"${demoted ? ' style="opacity:0.5"' : ''}> | |
| <div class="fc-prob-row"> | |
| <div class="fc-prob-label" | |
| style="border-left:2px solid ${catColor}47;padding-left:6px${demoted ? ';opacity:0.5' : ''}"> |
Alternatively, move the dimming to the individual text/bar cells so the sim-bar-wrap can opt out.
| .fc-sim-label { font-size: 9px; display: none; margin-top: 2px; line-height: 1.2; } | ||
| .fc-prob-item:hover .fc-sim-label { display: block; } |
There was a problem hiding this comment.
Hover label reveal causes row height jump
.fc-prob-row is a CSS Grid with align-items: center. When .fc-sim-label switches from display: none to display: block on hover, the first grid column (.fc-prob-label) grows taller, re-centering all other columns (probability bar, trend, domain tag) to the new row height. This produces a visible layout shift and can make adjacent cells feel jittery, particularly noticeable when quickly scanning forecasts.
Consider visibility: hidden + height: 1.2em instead of toggling display, so the row height is always reserved:
| .fc-sim-label { font-size: 9px; display: none; margin-top: 2px; line-height: 1.2; } | |
| .fc-prob-item:hover .fc-sim-label { display: block; } | |
| .fc-sim-label { font-size: 9px; visibility: hidden; height: 1.2em; margin-top: 2px; line-height: 1.2; } | |
| .fc-prob-item:hover .fc-sim-label { visibility: visible; } |
This pre-allocates the space even when the label is hidden and avoids the reflow.
Summary
Forecastproto fields:simulation_adjustment,sim_path_confidence,demoted_by_simulation(fields 20–22)ForecastPanel.ts: a thin 2px colored underbar below each forecast title that encodes simulation evidence without adding columns or visible text at restbuildPublishedForecastPayload(zero overhead — all fields default to 0/false when absent)Visual design
The sub-bar is deliberately quiet:
Hover labels use plain language — no "sim" jargon:
AI signal · +8%AI signal (moderate) · +12%AI caution · −12%AI flag: dropped · −15%Data plumbing
The rendering is fully implemented and typecheck-clean. The chip will not show in production until the ExpandedPath → Forecast plumbing lands. That follow-up requires:
applyPostSimulationRescoreto write sim decorations back to the published Redis forecast key (viastateUnit.forecastIds→candidateStateIdmapping)simulationAdjustment,simPathConfidence,demotedBySimulation) to be populated onpredobjects beforebuildPublishedForecastPayloadrunsThis PR adds the proto schema and UI code as the foundation. The plumbing is the next step.
Testing
node --test tests/forecast-trace-export.test.mjs→ 272/272 pass (no changes to test suite needed)npm run typecheck→ cleanPost-Deploy Monitoring & Validation