feat(lineage): document ADD/COLUMN graph badges in the changes legend (DRC-3466) - #1490
Conversation
The lineage canvas renders ADD and COLUMN treatment badges on nodes when the new CLL experience is on, but the change-status legend on the same canvas listed only Added / Removed / Modified / Impacted. Users had no in-view key for the badges, only a hover tooltip covering the whole node title row. - wholeModelTreatment.ts: export getGraphBadgeLegendEntries(isDark), which resolves all three graph badges from the same GRAPH_BADGE_LABELS and tokensForKind the node badges use, in a fixed legend display order. The classifiers are unchanged. - LineageLegend.tsx: behind newCllExperience, append a divider, a Badges caption, and one row per badge. Swatches reuse TreatmentChip so they match the node badges pixel for pixel; explanations render as visible text rather than another hover tooltip. Column-only change and column-only impact get separate rows: they share the COLUMN text but not the palette (brown vs amber), so a single row could only match half the badges a user sees. Swatch testIds are legend-treatment-<kind>, not the node testIds, which end in -badge and are structurally asserted to be absent in the node tests. The legend takes its colour mode from useIsDark rather than a new prop, so the public primitives export keeps its signature. DRC-3466 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jared Scott <jared.scott@datarecce.io>
Code Review: PR #1490SHA BlockersNone. Issues
Notes
Verification
LimitsThe formal |
| const GRAPH_BADGE_LEGEND_ORDER: GraphBadgeKind[] = [ | ||
| "additive", | ||
| "column-changed", | ||
| "column-impacted", | ||
| ]; |
There was a problem hiding this comment.
GRAPH_BADGE_LEGEND_ORDER is not exhaustiveness-checked against GRAPH_BADGE_LABELS, so a fourth GraphBadgeKind gets no legend row with tsc and all 22 tests green.
Deriving the order from GRAPH_BADGE_LABELS keys and ranking via a Record<GraphBadgeKind, number> makes the omission a compile error, and preserves the docstring's point — reading order stays independent of the classifier's precedence.
| const GRAPH_BADGE_LEGEND_ORDER: GraphBadgeKind[] = [ | |
| "additive", | |
| "column-changed", | |
| "column-impacted", | |
| ]; | |
| const GRAPH_BADGE_LEGEND_RANK: Record<GraphBadgeKind, number> = { | |
| additive: 0, | |
| "column-changed": 1, | |
| "column-impacted": 2, | |
| }; | |
| /** | |
| * Derived from `GRAPH_BADGE_LABELS` rather than hand-listed, so a new | |
| * `GraphBadgeKind` cannot reach the canvas without a legend row — the rank | |
| * record above stops compiling until it is ranked. | |
| */ | |
| const GRAPH_BADGE_LEGEND_ORDER: GraphBadgeKind[] = ( | |
| Object.keys(GRAPH_BADGE_LABELS) as GraphBadgeKind[] | |
| ).sort((a, b) => GRAPH_BADGE_LEGEND_RANK[a] - GRAPH_BADGE_LEGEND_RANK[b]); |
There was a problem hiding this comment.
Applied in d9beadaa, with the rank record split out so the docstring's original point (reading order independent of classifier precedence) stays explicit:
GRAPH_BADGE_LEGEND_RANK: Record<GraphBadgeKind, number>— adding a kind to the union is now a type error until it is ranked.GRAPH_BADGE_LEGEND_ORDERderived fromObject.keys(GRAPH_BADGE_LABELS)and sorted by that rank — once ranked, the legend row appears without further edits.
tsc --noEmit, biome check, and vitest related (1785 passed / 5 skipped) all clean at d9beadaa.
|
✅ Code Review Loop — fix pushed (
Verified at No blocking findings remain. The review state could not be flipped to approve — |
… is unranked GRAPH_BADGE_LEGEND_ORDER was a hand-listed GraphBadgeKind[], which accepts a partial list, so a new badge kind could reach the canvas with no legend row and a green build. Derive the order from GRAPH_BADGE_LABELS keys and rank via a Record<GraphBadgeKind, number> so an unranked kind is a type error. Signed-off-by: Jared Scott <jared.scott@datarecce.io>
Closes DRC-3466.
Problem
With the new CLL experience on, lineage canvas nodes render
ADDandCOLUMNtreatment badges next to the model name. The change-status legend on the same canvas listed only Added / Removed / Modified / Impacted, so users saw badges with no in-view key — right where they'd look to decode them. The only explanation was a hover tooltip covering the whole node title row.(The originally-reported
ALLbadge no longer exists on the canvas; whole-model treatment moved to the NodeView title chip and stripe. This PR covers the badges that remain.)Change
wholeModelTreatment.ts— newgetGraphBadgeLegendEntries(isDark)returning all threeGraphBadgeResolutions in a fixed display order (benign → actionable), built from the same privateGRAPH_BADGE_LABELSandtokensForKindthe node badges use.pickGraphBadgeandpickTitleChipare untouched.legend/LineageLegend.tsx— behindvariant === "changeStatus" && newCllExperience, append a divider, aBadgescaption, and one row per badge. Swatches are<TreatmentChip variant="badge">, so they match the node badges exactly rather than approximating them.Rendered order with the flag on:
Three decisions worth reviewing
Two COLUMN rows, not one. The issue left this open.
column-changedandcolumn-impactedshare theCOLUMNtext but not the palette — brown vs amber — so a single row could only match half the badges a user actually sees. Two rows costs three lines of panel height and makes every swatch honest.Explanations are visible text, not tooltips. The copy comes from each entry's
tooltipfield but renders inline. Fixing "you have to discover a hover" by adding another hover would have missed the point.useIsDark()rather than a new prop.LineageLegendis a publicprimitives.tsexport, so a requiredisDarkprop would be a breaking API change. The hook matches sibling components (NodeTag,ColumnLevelLineageControl,LineageViewTopBar) and returnsfalseoutside a provider, so existing tests were unaffected. The component's "pure presentation" docstring was updated to say so.Swatch testIds are
legend-treatment-<kind>, deliberately not the node testIds — those end in-badge, andLineageNode.test.tsx/NodeView.test.tsxassert structurally that[data-testid$="-badge"]count is 0 for no-badge cases. Those queries are container-scoped so reuse couldn't break them today, but it would be a trap for any future full-view test.Acceptance criteria
ADDentry, chip matches node badge, explains additive changedocuments the ADD badgedocuments column-only change and column-only impact separatelyomits the badge rows when the flag is off+ 3 pre-existing tests unchangedGRAPH_BADGE_LABELSsources every row from the graph badge definitionsPlus
reads statuses first, then the badge block, a whole-legendtextContentassertion that locks the approved layout.Verification
pnpm test— 3982 passed, 5 skipped (187 files)pnpm type:check— cleanpnpm lint— cleanscripts/check_wire_enum_literals.sh— cleanChangeStatusClldocstring updated; newChangeStatusCllDarkfor the dark-mode tokensScope
Out:
SchemaLegendon the Columns tab (the wrong surface, per closed #1434). Badge behavior or classification changes (DRC-3814 / DRC-3815, which may later revise this legend's copy). A legend entry for the NodeView whole-model title chip.Follow-up worth filing: the legend only renders when
isModelsChangedis true, so a node can carry aCOLUMNimpact badge in states where no legend is on screen. Pre-existing, unrelated to this change, but it means the legend isn't a complete key in every state.🤖 Generated with Claude Code