Ripple
What the change touches, as a row: added, edited, removed; what re-hashed because of them; the tests they reach.
import { leafOf } from "#codebase/tcmts/share"; import type { ContributionChanges } from "#codebase/tcmts/share"; export function Ripple({ ch }: { ch: ContributionChanges }) { const items = ch.items; const col = (cls: string, names: string[]) => names.length ? ( <div className="col"> {names.slice(0, 3).map((n) => ( <span key={n} className={`node ${cls}`}> {leafOf(n)} </span> ))} {names.length > 3 && <span className={`node ${cls}`}>+{names.length - 3}</span>} </div> ) : null; const rehashed = ch.propagated.filter((p) => !p.test).length; const cols = [ col( "a", items.filter((i) => i.change === "added" && !i.test).map((i) => i.path), ), col( "e", items .filter((i) => ["edited", "reworded", "renamed"].includes(i.change) && !i.test) .map((i) => i.path), ), col( "r", items.filter((i) => i.change === "removed").map((i) => i.path), ), rehashed ? ( <div className="col"> <span className="node p">{rehashed} re-hashed</span> </div> ) : null, col("t", [ ...items.filter((i) => i.test && i.change !== "removed").map((i) => i.path), ...ch.propagated.filter((p) => p.test).map((p) => p.path), ]), ].filter((x) => x !== null); return ( <div className="box"> <div className="boxhead"> <b>What it touches</b> <span className="muted"> added and edited, what re-hashed because of them, and the tests they reach </span> </div> <div className="ripple"> {cols.map((x, i) => ( <span key={i} style={{ display: "contents" }}> {i > 0 && <span className="edge" />} {x} </span> ))} </div> </div> ); }