feat(forkchoice): add color legend to fork choice viz#338
feat(forkchoice): add color legend to fork choice viz#338mvanhorn wants to merge 1 commit intolambdaclass:mainfrom
Conversation
Greptile SummaryThis PR adds a four-swatch color legend to the Confidence Score: 4/5Safe to merge; all findings are non-blocking P2 suggestions. No logic changes, only static HTML/CSS additions. P2 findings only: missing default-color swatch, ineffective aria-label, and undocumented coupling between inline hex values and the JS COLORS object. crates/net/rpc/static/fork_choice.html — legend completeness and accessibility attributes.
|
| Filename | Overview |
|---|---|
| crates/net/rpc/static/fork_choice.html | Adds a static color-legend bar to the fork-choice viz; CSS and HTML are well-structured, but the legend omits the default (#666) node color, the aria-label has no role, and inline hex values are not linked to the JS COLORS object. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["nodeColor(node, data)"] --> B{node.root == data.head?}
B -- yes --> C["COLORS.head #ff9800"]
B -- no --> D{node.root == data.safe_target?}
D -- yes --> E["COLORS.safeTarget #ffeb3b"]
D -- no --> F{node.root == data.justified.root?}
F -- yes --> G["COLORS.justified #2196f3"]
F -- no --> H{node.slot <= data.finalized.slot?}
H -- yes --> I["COLORS.finalized #4caf50"]
H -- no --> J["COLORS.default #666 (not in legend)"]
subgraph Legend["New Legend"]
L1["Finalized #4caf50"]
L2["Justified #2196f3"]
L3["Safe target #ffeb3b"]
L4["Head #ff9800"]
end
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
crates/net/rpc/static/fork_choice.html:185-190
**Legend omits the default (grey) node color**
`nodeColor()` returns `COLORS.default` (`#666`) for any block that isn't head, safe-target, justified, or finalized. Those grey nodes will appear in the viz but aren't explained by the legend, leaving users in the same "have to guess" situation the legend is meant to fix. Adding a fifth swatch for the pending/default state would make the legend complete.
```suggestion
<div id="legend" aria-label="Block color legend" role="region">
<div class="legend-item"><span class="legend-swatch" style="background: #4caf50;"></span>Finalized</div>
<div class="legend-item"><span class="legend-swatch" style="background: #2196f3;"></span>Justified</div>
<div class="legend-item"><span class="legend-swatch" style="background: #ffeb3b;"></span>Safe target</div>
<div class="legend-item"><span class="legend-swatch" style="background: #ff9800;"></span>Head</div>
<div class="legend-item"><span class="legend-swatch" style="background: #666;"></span>Default</div>
</div>
```
### Issue 2 of 3
crates/net/rpc/static/fork_choice.html:185
**`aria-label` ineffective without a role on a generic `div`**
Screen readers only announce `aria-label` on elements that have an implicit or explicit ARIA role. A plain `<div>` has no role, so the label `"Block color legend"` will be silently ignored by most assistive technology. Adding `role="region"` turns this into a named landmark that screen readers will announce.
### Issue 3 of 3
crates/net/rpc/static/fork_choice.html:186-189
**Inline hex values will silently drift from `COLORS`**
The swatch colors are hardcoded as inline `style` attributes. The file already has `.value-head`, `.value-justified`, and `.value-finalized` CSS classes whose `color` values are kept in sync with the JS `COLORS` object. If `COLORS` is updated, the legend swatches won't change automatically. Consider using a CSS custom property or at minimum a comment like `/* must match COLORS in JS */` to signal the coupling.
Reviews (1): Last reviewed commit: "feat(forkchoice): add color legend to fo..." | Re-trigger Greptile
| <div id="legend" aria-label="Block color legend"> | ||
| <div class="legend-item"><span class="legend-swatch" style="background: #4caf50;"></span>Finalized</div> | ||
| <div class="legend-item"><span class="legend-swatch" style="background: #2196f3;"></span>Justified</div> | ||
| <div class="legend-item"><span class="legend-swatch" style="background: #ffeb3b;"></span>Safe target</div> | ||
| <div class="legend-item"><span class="legend-swatch" style="background: #ff9800;"></span>Head</div> | ||
| </div> |
There was a problem hiding this comment.
Legend omits the default (grey) node color
nodeColor() returns COLORS.default (#666) for any block that isn't head, safe-target, justified, or finalized. Those grey nodes will appear in the viz but aren't explained by the legend, leaving users in the same "have to guess" situation the legend is meant to fix. Adding a fifth swatch for the pending/default state would make the legend complete.
| <div id="legend" aria-label="Block color legend"> | |
| <div class="legend-item"><span class="legend-swatch" style="background: #4caf50;"></span>Finalized</div> | |
| <div class="legend-item"><span class="legend-swatch" style="background: #2196f3;"></span>Justified</div> | |
| <div class="legend-item"><span class="legend-swatch" style="background: #ffeb3b;"></span>Safe target</div> | |
| <div class="legend-item"><span class="legend-swatch" style="background: #ff9800;"></span>Head</div> | |
| </div> | |
| <div id="legend" aria-label="Block color legend" role="region"> | |
| <div class="legend-item"><span class="legend-swatch" style="background: #4caf50;"></span>Finalized</div> | |
| <div class="legend-item"><span class="legend-swatch" style="background: #2196f3;"></span>Justified</div> | |
| <div class="legend-item"><span class="legend-swatch" style="background: #ffeb3b;"></span>Safe target</div> | |
| <div class="legend-item"><span class="legend-swatch" style="background: #ff9800;"></span>Head</div> | |
| <div class="legend-item"><span class="legend-swatch" style="background: #666;"></span>Default</div> | |
| </div> |
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/net/rpc/static/fork_choice.html
Line: 185-190
Comment:
**Legend omits the default (grey) node color**
`nodeColor()` returns `COLORS.default` (`#666`) for any block that isn't head, safe-target, justified, or finalized. Those grey nodes will appear in the viz but aren't explained by the legend, leaving users in the same "have to guess" situation the legend is meant to fix. Adding a fifth swatch for the pending/default state would make the legend complete.
```suggestion
<div id="legend" aria-label="Block color legend" role="region">
<div class="legend-item"><span class="legend-swatch" style="background: #4caf50;"></span>Finalized</div>
<div class="legend-item"><span class="legend-swatch" style="background: #2196f3;"></span>Justified</div>
<div class="legend-item"><span class="legend-swatch" style="background: #ffeb3b;"></span>Safe target</div>
<div class="legend-item"><span class="legend-swatch" style="background: #ff9800;"></span>Head</div>
<div class="legend-item"><span class="legend-swatch" style="background: #666;"></span>Default</div>
</div>
```
How can I resolve this? If you propose a fix, please make it concise.| <span class="label">Validators</span> | ||
| <span class="value value-validators" id="validator-count">--</span> | ||
| </div> | ||
| <div id="legend" aria-label="Block color legend"> |
There was a problem hiding this comment.
aria-label ineffective without a role on a generic div
Screen readers only announce aria-label on elements that have an implicit or explicit ARIA role. A plain <div> has no role, so the label "Block color legend" will be silently ignored by most assistive technology. Adding role="region" turns this into a named landmark that screen readers will announce.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/net/rpc/static/fork_choice.html
Line: 185
Comment:
**`aria-label` ineffective without a role on a generic `div`**
Screen readers only announce `aria-label` on elements that have an implicit or explicit ARIA role. A plain `<div>` has no role, so the label `"Block color legend"` will be silently ignored by most assistive technology. Adding `role="region"` turns this into a named landmark that screen readers will announce.
How can I resolve this? If you propose a fix, please make it concise.| <div class="legend-item"><span class="legend-swatch" style="background: #4caf50;"></span>Finalized</div> | ||
| <div class="legend-item"><span class="legend-swatch" style="background: #2196f3;"></span>Justified</div> | ||
| <div class="legend-item"><span class="legend-swatch" style="background: #ffeb3b;"></span>Safe target</div> | ||
| <div class="legend-item"><span class="legend-swatch" style="background: #ff9800;"></span>Head</div> |
There was a problem hiding this comment.
Inline hex values will silently drift from
COLORS
The swatch colors are hardcoded as inline style attributes. The file already has .value-head, .value-justified, and .value-finalized CSS classes whose color values are kept in sync with the JS COLORS object. If COLORS is updated, the legend swatches won't change automatically. Consider using a CSS custom property or at minimum a comment like /* must match COLORS in JS */ to signal the coupling.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/net/rpc/static/fork_choice.html
Line: 186-189
Comment:
**Inline hex values will silently drift from `COLORS`**
The swatch colors are hardcoded as inline `style` attributes. The file already has `.value-head`, `.value-justified`, and `.value-finalized` CSS classes whose `color` values are kept in sync with the JS `COLORS` object. If `COLORS` is updated, the legend swatches won't change automatically. Consider using a CSS custom property or at minimum a comment like `/* must match COLORS in JS */` to signal the coupling.
How can I resolve this? If you propose a fix, please make it concise.
Pull Request Template
🗒️ Description / Motivation
Adds a four-swatch color legend to the right side of the
info-baron the fork choice visualization (crates/net/rpc/static/fork_choice.html).Issue #335 lists the four colors the viz uses and asks for an in-page legend so users do not have to guess which color means what. The mapping lives in the
COLORSobject inside the page script (lines 178-181) and is never surfaced in the rendered UI today, so anyone looking at the viz either has to know the convention from project docs or open dev tools and read the JS source. A small swatch-and-label list in the existing top bar fixes that without changing any of the actual viz logic.What Changed
crates/net/rpc/static/fork_choice.html:#legend,.legend-item, and.legend-swatchCSS rules in the existing<style>block. The legend usesmargin-left: autoso it pins to the right edge of the#info-bar, andflex-wrap: wrapso it relaxes onto a second line on narrow viewports without breaking the existing Head/Justified/Finalized/Validators value cards.<div id="legend" aria-label="Block color legend">element after theValidatorsitem inside#info-bar. Four.legend-itemrows, one per role: Finalized (#4caf50), Justified (#2196f3), Safe target (#ffeb3b), Head (#ff9800). Hex values are copied from theCOLORSobject in the existing JS so the swatches cannot drift from the actual rendered colors. Label text uses the same casing convention (uppercase, 11px,#888) the existing info-bar labels use.No JavaScript changes. The
COLORSobject stays the single source of truth for what each role renders as; the legend is hand-keyed to match.Correctness / Behavior Guarantees
nodeColor(node, data)(line 209) using the sameCOLORSobject that drives the new legend swatches.#info-barflex container is preserved. The legend is appended after theValidatorsitem; the existing four value cards keep their original order and alignment.aria-label="Block color legend"is set on the legend container so screen readers announce the section.flex-wrap: wrapon#info-barwas already present in the existing styles, so the legend wrapping onto a second line on narrow viewports does not change layout behavior elsewhere.Tests Added / Run
fork_choice.htmlis a static asset served directly by the RPC layer (GET /lean/v0/fork_choice/ui). There is no compile or asset build step to run, so the existing test suite does not cover it.I did not bring up a node + browser locally to confirm the legend renders (no devnet available in this environment). The swatch hex values are byte-equal to the
.value-*CSS rules and the JSCOLORSobject that already exist in the file, so the displayed swatches cannot drift from the actual node colors. After the change:matches the four entries in the existing
COLORS = { finalized, justified, safeTarget, head }declaration.Related Issues / PRs