From 3734d9af431a190bcdc661b59878974c264ba419 Mon Sep 17 00:00:00 2001 From: Daniel F B Morinigo Date: Mon, 11 May 2026 03:37:46 +0000 Subject: [PATCH 1/3] feat(roadmap-planner): chart help icons on Team Analytics Dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a small info button next to every chart title (and the KPI strip) on the Team Analytics → Dashboard tab. Clicking it toggles a popover that explains what the chart shows, how its data is derived, how to read the deltas/legend, and how the active-metric selection cascades through the page. Covered: KPI tiles · Throughput trend · Pillar mix over time · Inflow vs outflow · Distribution by pillar · Top movers · Per-member ranking. Click-outside / Esc closes. Styling uses only the existing semantic theme tokens so light/dark/Atlas all paint correctly. --- .../frontend/src/components/TeamAnalytics.css | 93 ++++++++++ .../src/components/TeamAnalyticsDashboard.jsx | 172 +++++++++++++++--- 2 files changed, 241 insertions(+), 24 deletions(-) diff --git a/roadmap-planner/frontend/src/components/TeamAnalytics.css b/roadmap-planner/frontend/src/components/TeamAnalytics.css index e9d703eb..489fb22e 100644 --- a/roadmap-planner/frontend/src/components/TeamAnalytics.css +++ b/roadmap-planner/frontend/src/components/TeamAnalytics.css @@ -799,6 +799,99 @@ .ta-bars { display: flex; flex-direction: column; gap: 6px; } +/* ---------- Chart help popovers ---------- + * Small info-icon button next to a chart title that toggles a popover + * with prose explaining what the chart shows. The wrapper is positioned + * (relative) so the popover anchors to the icon. Panels use + * `overflow: hidden` for clean borders, so the popover is positioned + * relatively but uses a high z-index and a wide box; it can extend + * below the panel head over the chart body (panels are tall enough). */ +.ta-help { + display: inline-flex; + align-items: center; + position: relative; + margin-left: 8px; + vertical-align: middle; +} +/* When a help popover is open, let it escape the panel's clipped frame + * (panels use overflow:hidden so their head background stays inside the + * rounded corners). :has() flips that off only for the open panel, so + * the popover can extend over the chart area below the head. */ +.ta-panel:has(.ta-help__pop) { overflow: visible; } +.ta-help__btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + padding: 0; + border: 1px solid var(--border-soft); + border-radius: 999px; + background: var(--bg-elevated); + color: var(--fg-muted); + cursor: pointer; + transition: + color var(--dur-fast) var(--ease), + border-color var(--dur-fast) var(--ease), + background var(--dur-fast) var(--ease); +} +.ta-help__btn:hover, +.ta-help__btn[aria-expanded="true"] { + color: var(--accent); + border-color: var(--accent); + background: var(--accent-tint); +} +.ta-help__pop { + position: absolute; + top: calc(100% + 8px); + left: 0; + z-index: 30; + width: min(420px, 80vw); + padding: 14px 16px; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-default, 4px); + box-shadow: var(--shadow-plate); + font-family: var(--font-ui); + font-size: 12.5px; + line-height: 1.55; + color: var(--fg); + text-transform: none; + letter-spacing: 0; + font-weight: 400; + font-style: normal; +} +.ta-help__pop-title { + font-family: var(--font-mono); + font-size: 10.5px; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--fg-muted); + margin-bottom: 8px; +} +.ta-help__pop-body p { margin: 0 0 8px; } +.ta-help__pop-body p:last-child { margin-bottom: 0; } +.ta-help__pop-body ul { + margin: 0 0 8px; + padding-left: 18px; +} +.ta-help__pop-body li { margin-bottom: 4px; } +.ta-help__pop-body strong { color: var(--fg); font-weight: 600; } +.ta-help__pop-body em { color: var(--fg-subtle); font-style: italic; } + +/* KPI-strip help anchor: keeps the icon to the right of the strip + * without breaking the 5-column grid. */ +.ta-kpis-wrap { + display: flex; + align-items: flex-start; + gap: 8px; + margin-bottom: 6px; +} +.ta-kpis-wrap .ta-kpis { flex: 1; margin-bottom: 0; } +.ta-kpis-help { + padding-top: 16px; /* visually align with KPI tile labels */ +} + /* Donut layout */ .ta-donut-row { display: flex; diff --git a/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx b/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx index 15ded8fa..5e353244 100644 --- a/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx +++ b/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx @@ -18,9 +18,47 @@ * — no hex literals, no theme branching here. Light/Dark/Atlas swap * automatically. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { HelpCircle } from 'lucide-react'; import { contributionsAPI } from '../services/api'; +/* ChartHelp — small info-icon button that toggles a popover with prose + * explaining what a chart shows, how its metric is derived, and how to + * read it. Click-outside or Escape closes. Anchored to the panel head + * so the popover doesn't get clipped by the panel's overflow:hidden. */ +function ChartHelp({ title, children }) { + const [open, setOpen] = useState(false); + const ref = useRef(null); + useEffect(() => { + if (!open) return undefined; + const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; + const onKey = (e) => { if (e.key === 'Escape') setOpen(false); }; + document.addEventListener('mousedown', onDown); + document.addEventListener('keydown', onKey); + return () => { + document.removeEventListener('mousedown', onDown); + document.removeEventListener('keydown', onKey); + }; + }, [open]); + return ( + + + {open && ( +
+
{title}
+
{children}
+
+ )} +
+ ); +} + const METRIC_OPTIONS = [ { val: 'prs', label: 'PRs merged' }, { val: 'opened', label: 'PRs opened' }, @@ -455,24 +493,40 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick -
- {kpis.map((k) => ( - - ))} +
+
+ {kpis.map((k) => ( + + ))} +
+
+ +

Five clickable tiles, one per metric. Each tile shows the team‑wide total for the selected window and filter, plus a delta vs the prior half of the same window.

+
    +
  • PRs merged — pull/merge requests with a merged state. The headline throughput metric.
  • +
  • PRs opened — PRs/MRs created in the period, regardless of whether they were merged.
  • +
  • Reviews — code reviews left on others' PRs (non‑self review comments / approvals).
  • +
  • Jira done — Jira issues transitioned to a Done state (resolution timestamp falls in the window).
  • +
  • Story pts — sum of story points on the Jira issues counted above.
  • +
+

The arrow + percentage compares the most recent half of the window to the earlier half (e.g. for 26 weeks: last 13 vs prior 13). (new) means there was zero activity in the prior half.

+

Click any tile to make that metric the active metric — the Throughput trend highlights its line, and the Pillar mix, Donut, Top movers, and Ranking panels all switch to it.

+
+

Click a metric tile to drive the panels below.

@@ -482,6 +536,16 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick
Throughput trend · {METRIC_OPTIONS.find((o) => o.val === metric)?.label} + +

Weekly team‑wide totals for all five metrics on the same axis, so you can spot when activity surges, dips, or rotates between PR throughput, reviews, and Jira completion.

+
    +
  • Each line is the sum across the currently filtered members for that ISO week (Monday‑to‑Sunday, UTC).
  • +
  • The active metric's line is drawn bold with dots; the others are dimmed for reference. Click any tile or legend entry to switch which line is highlighted.
  • +
  • The Y axis auto‑scales to the largest series across all five metrics — so a small metric next to a large one will look flat. That's intentional: it preserves cross‑metric comparison.
  • +
  • X‑axis labels show MM‑DD of every 4th week plus the latest week.
  • +
+

Look for sustained slopes (capacity changes), notch‑shaped weeks (releases, holidays), and divergence between PRs opened and PRs merged (queue buildup vs catch‑up).

+
Team aggregate · weekly
@@ -504,7 +568,19 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick
-
Pillar mix over time
+
+ Pillar mix over time + +

Stacked area showing how the active metric is distributed across pillars, week by week. Each colored band is one pillar; the total height is the team aggregate for that week.

+
    +
  • A member is counted under their primary pillar — derived on the backend from the Jira components their work touches (with a fixVersion‑prefix fallback). Members with no signal yet show under Unassigned.
  • +
  • Bands only appear for pillars that actually contributed in the window; empty pillars are dropped.
  • +
  • Changing the active metric reshapes the whole chart — story points and PRs merged don't share scale.
  • +
  • The Pillar filter at the top still applies. With a single pillar selected, this becomes a single‑band area chart for that pillar's trend.
  • +
+

Use it to see whether a single pillar is carrying total throughput, or whether the mix is rotating across teams over time.

+
+
Stacked area · {METRIC_OPTIONS.find((o) => o.val === metric)?.label}
@@ -524,7 +600,19 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick
-
Inflow vs outflow
+
+ Inflow vs outflow + +

Paired weekly bars: PRs opened (left, blue) vs PRs merged (right, red). This panel is intentionally fixed to opened‑vs‑merged — it answers "is the team merging what it's opening?" and the question doesn't translate to other metrics.

+
    +
  • Opened > Merged for many weeks → queue is growing; PRs are piling up faster than they ship.
  • +
  • Merged > Opened → the team is catching up on an existing backlog, or shipping work opened earlier in the window.
  • +
  • Roughly balanced → steady state.
  • +
  • Both at zero → a quiet week (holiday, freeze, release pause).
  • +
+

Both bars respect the Window and Pillar filters but ignore the active metric selection.

+
+
PRs opened (blue) vs PRs merged (red) · per week
@@ -539,7 +627,18 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick
-
Distribution by pillar
+
+ Distribution by pillar + +

Donut chart of the active metric summed over the whole window, sliced by pillar. The number in the center is the team total; each legend row shows the pillar's count and percent of total.

+
    +
  • Same pillar derivation as the Pillar mix chart — primary pillar per member, from Jira component matches with a fixVersion‑prefix fallback.
  • +
  • Pillars with zero contribution are omitted from both the donut and the legend.
  • +
  • Slices are sorted largest‑first so the biggest contributor is always at 12 o'clock.
  • +
+

Where Pillar mix over time shows when a pillar contributes, this shows how much in aggregate. Use it to sanity‑check whether one pillar dominates the metric you're looking at.

+
+
{METRIC_OPTIONS.find((o) => o.val === metric)?.label}
@@ -561,7 +660,20 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick
-
Top movers
+
+ Top movers + +

Per‑member delta on the active metric: the recent half of the window minus the prior half. Top 8 by absolute delta — biggest swings in either direction, not biggest totals.

+
    +
  • Bar length is normalized to the largest absolute delta in the list.
  • +
  • Numeric label: +N means the member's count grew compared to the prior half; a bare N (no sign) means it dropped; ± means it didn't move.
  • +
  • Bar color reflects direction — the active metric's color for growth, muted gray for a drop — so improvements and slowdowns are visually distinct.
  • +
  • Members with zero activity in both halves are excluded.
  • +
  • Click any row to jump to that member's profile.
  • +
+

Best read together with the ranking panel below: a member can rank near the top of the team yet have a negative mover delta (great in absolute terms, slowing down), or vice‑versa.

+
+
Recent half vs prior half · per-member delta
@@ -589,7 +701,19 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick
-
Per-member ranking
+
+ Per-member ranking + +

Every member who contributed at least one unit of the active metric in the window, sorted high to low. Bar length is the member's total; the number on the right is the raw count.

+
    +
  • The pillar tag next to each name uses the same pillar derivation as the Pillar mix and Donut charts. The bar is colored with that pillar's swatch, so the ranking doubles as a quick pillar‑contribution view.
  • +
  • Filtered by the Window and Pillar selectors at the top — pick a pillar to get a leaderboard within that pillar.
  • +
  • Members with zero activity for the active metric are hidden; switching to a different metric will reshuffle (and may shrink) the list.
  • +
  • Click a row to drill into that member's profile.
  • +
+

This is a totals view. For who's accelerating vs slowing down, use the Top movers panel above.

+
+
Selected window · {METRIC_OPTIONS.find((o) => o.val === metric)?.label} · click to drill into profile
From dec8ecea6e30665605e41ae67ef9eb11330b6af7 Mon Sep 17 00:00:00 2001 From: Daniel F B Morinigo Date: Mon, 11 May 2026 04:11:10 +0000 Subject: [PATCH 2/3] fix(roadmap-planner): distinct pillar colors + move KPI help to hint line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups based on dev-preview feedback: 1. Pillar palette: the 5-token semantic palette (accent/ocean/amber/ forest/crimson) collapses to 4 distinct colors in the default light theme — both --accent and --ocean resolve to blue, so the first two pillars looked identical. With 7+ pillars in the DEVOPS project the modulo wrap also forced extra duplicates. Swap to a Tableau-10 inspired 11-color categorical palette tuned for perceptual distance under the 0.85 paint opacity, in both TeamAnalyticsDashboard.jsx and TeamAnalytics.jsx so the Dashboard donut/stack/ranking and the Team Overview throughput-by-pillar chart stay in sync. 2. Move the Metric KPI tiles help icon out of the right edge of the tile strip and inline it with the hint line below it, so the icon sits next to "Click a metric tile to drive the panels below." instead of breaking the 5-column tile grid. --- .../frontend/src/components/TeamAnalytics.css | 14 +-- .../frontend/src/components/TeamAnalytics.jsx | 23 ++++- .../src/components/TeamAnalyticsDashboard.jsx | 91 +++++++++++-------- 3 files changed, 82 insertions(+), 46 deletions(-) diff --git a/roadmap-planner/frontend/src/components/TeamAnalytics.css b/roadmap-planner/frontend/src/components/TeamAnalytics.css index 489fb22e..a3013115 100644 --- a/roadmap-planner/frontend/src/components/TeamAnalytics.css +++ b/roadmap-planner/frontend/src/components/TeamAnalytics.css @@ -879,17 +879,13 @@ .ta-help__pop-body strong { color: var(--fg); font-weight: 600; } .ta-help__pop-body em { color: var(--fg-subtle); font-style: italic; } -/* KPI-strip help anchor: keeps the icon to the right of the strip - * without breaking the 5-column grid. */ -.ta-kpis-wrap { +/* KPI-strip hint row sits directly under the 5-tile grid and carries + * the help icon inline. Flex layout keeps the icon vertically centered + * against the small-caps hint text. */ +.ta-kpis-hint { display: flex; - align-items: flex-start; + align-items: center; gap: 8px; - margin-bottom: 6px; -} -.ta-kpis-wrap .ta-kpis { flex: 1; margin-bottom: 0; } -.ta-kpis-help { - padding-top: 16px; /* visually align with KPI tile labels */ } /* Donut layout */ diff --git a/roadmap-planner/frontend/src/components/TeamAnalytics.jsx b/roadmap-planner/frontend/src/components/TeamAnalytics.jsx index 24a14b16..5795a185 100644 --- a/roadmap-planner/frontend/src/components/TeamAnalytics.jsx +++ b/roadmap-planner/frontend/src/components/TeamAnalytics.jsx @@ -34,7 +34,28 @@ import './TeamAnalytics.css'; // properties resolve at paint time against the active [data-theme] / // [data-mode], so dark mode and the Atlas theme need no per-mode // branching here. -const PILLAR_PALETTE = ['var(--accent)', 'var(--ocean)', 'var(--amber)', 'var(--forest)', 'var(--crimson)']; +// Categorical palette for pillar bands. The semantic theme tokens +// (--accent / --ocean / …) don't work here: in the default light theme +// --accent and --ocean both resolve to blue (first two slots collide), +// and the project routinely has more pillars than the 5-token semantic +// palette, so modulo wrapping causes explicit duplicates. These values +// are tuned for perceptual distance (Tableau-10 inspired) and survive +// the 0.85 opacity used by the stacked-area paint. Pillar colors carry +// no theme semantics — they're purely categorical codes — so fixed +// values are appropriate. Keep in sync with TeamAnalyticsDashboard.jsx. +const PILLAR_PALETTE = [ + '#4E79A7', // blue + '#F28E2B', // orange + '#59A14F', // green + '#E15759', // red + '#B07AA1', // purple + '#EDC948', // yellow + '#76B7B2', // teal + '#FF9DA7', // pink + '#9C755F', // brown + '#17BECF', // cyan + '#BAB0AC', // gray +]; const formatHours = (h) => (h == null ? '—' : `${(+h).toFixed(1)}h`); const formatPct = (p) => (p == null || isNaN(p) ? '—' : `${Math.round(p)}%`); diff --git a/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx b/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx index 5e353244..5e9be6e0 100644 --- a/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx +++ b/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx @@ -76,7 +76,28 @@ const METRIC_COLOR = { points: 'var(--crimson)', }; -const PILLAR_PALETTE = ['var(--accent)', 'var(--ocean)', 'var(--amber)', 'var(--forest)', 'var(--crimson)']; +// Categorical palette for pillar bands. Theme semantic tokens fall short +// here for two reasons: (1) in the default light theme --accent and +// --ocean both resolve to blue, so two pillars sharing the first two +// slots look identical; (2) the DEVOPS project routinely has 7+ pillars, +// past the 5-token semantic palette, and modulo wrapping forces explicit +// duplicates. These values are tuned for perceptual distance (Tableau-10 +// inspired) and survive the 0.85 opacity used by the stacked-area / +// donut paints. Fixed values are fine here because pillar colors carry +// no theme semantics — they're purely categorical codes. +const PILLAR_PALETTE = [ + '#4E79A7', // blue + '#F28E2B', // orange + '#59A14F', // green + '#E15759', // red + '#B07AA1', // purple + '#EDC948', // yellow + '#76B7B2', // teal + '#FF9DA7', // pink + '#9C755F', // brown + '#17BECF', // cyan + '#BAB0AC', // gray +]; const colorForPillar = (pillarName, allPillars) => { if (!pillarName || pillarName === 'Unassigned') return 'var(--fg-faint)'; const idx = (allPillars || []).indexOf(pillarName); @@ -493,42 +514,40 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick
-
-
- {kpis.map((k) => ( - - ))} -
-
- -

Five clickable tiles, one per metric. Each tile shows the team‑wide total for the selected window and filter, plus a delta vs the prior half of the same window.

-
    -
  • PRs merged — pull/merge requests with a merged state. The headline throughput metric.
  • -
  • PRs opened — PRs/MRs created in the period, regardless of whether they were merged.
  • -
  • Reviews — code reviews left on others' PRs (non‑self review comments / approvals).
  • -
  • Jira done — Jira issues transitioned to a Done state (resolution timestamp falls in the window).
  • -
  • Story pts — sum of story points on the Jira issues counted above.
  • -
-

The arrow + percentage compares the most recent half of the window to the earlier half (e.g. for 26 weeks: last 13 vs prior 13). (new) means there was zero activity in the prior half.

-

Click any tile to make that metric the active metric — the Throughput trend highlights its line, and the Pillar mix, Donut, Top movers, and Ranking panels all switch to it.

-
-
+
+ {kpis.map((k) => ( + + ))} +
+
+ Click a metric tile to drive the panels below. + +

Five clickable tiles, one per metric. Each tile shows the team‑wide total for the selected window and filter, plus a delta vs the prior half of the same window.

+
    +
  • PRs merged — pull/merge requests with a merged state. The headline throughput metric.
  • +
  • PRs opened — PRs/MRs created in the period, regardless of whether they were merged.
  • +
  • Reviews — code reviews left on others' PRs (non‑self review comments / approvals).
  • +
  • Jira done — Jira issues transitioned to a Done state (resolution timestamp falls in the window).
  • +
  • Story pts — sum of story points on the Jira issues counted above.
  • +
+

The arrow + percentage compares the most recent half of the window to the earlier half (e.g. for 26 weeks: last 13 vs prior 13). (new) means there was zero activity in the prior half.

+

Click any tile to make that metric the active metric — the Throughput trend highlights its line, and the Pillar mix, Donut, Top movers, and Ranking panels all switch to it.

+
-

Click a metric tile to drive the panels below.

From 36b8e119bb5850550fb77dc0cfcc236c90cc5f12 Mon Sep 17 00:00:00 2001 From: Daniel F B Morinigo Date: Mon, 11 May 2026 04:36:12 +0000 Subject: [PATCH 3/3] fix(roadmap-planner): make metric line + pillar colors fully distinct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related collisions surfaced in the dev preview: 1. METRIC_COLOR mapped prs→--accent and opened→--ocean. Both tokens resolve to the same blue in the default light theme, so the PRs merged and PRs opened lines on the Throughput trend, plus the Inflow vs outflow bars (which hardcoded the same two vars), all rendered as a single blue. Swap to d3 schemeCategory10 hex values for guaranteed distinctness across light / dark / Atlas. Update Inflow vs outflow + its description to route through METRIC_COLOR. 2. Pillar palette: tighten the two soft slots that still read as ambiguous at 0.85 opacity — replace Tableau's muted #B07AA1 purple with #8E44AD and Tableau's #FF9DA7 pink with #E377C2 magenta, so the purple / pink / red triplet stays clearly separated. --- .../frontend/src/components/TeamAnalytics.jsx | 6 ++-- .../src/components/TeamAnalyticsDashboard.jsx | 29 +++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/roadmap-planner/frontend/src/components/TeamAnalytics.jsx b/roadmap-planner/frontend/src/components/TeamAnalytics.jsx index 5795a185..3eeed9a7 100644 --- a/roadmap-planner/frontend/src/components/TeamAnalytics.jsx +++ b/roadmap-planner/frontend/src/components/TeamAnalytics.jsx @@ -48,13 +48,13 @@ const PILLAR_PALETTE = [ '#F28E2B', // orange '#59A14F', // green '#E15759', // red - '#B07AA1', // purple + '#8E44AD', // purple '#EDC948', // yellow '#76B7B2', // teal - '#FF9DA7', // pink + '#E377C2', // magenta '#9C755F', // brown '#17BECF', // cyan - '#BAB0AC', // gray + '#7F7F7F', // gray ]; const formatHours = (h) => (h == null ? '—' : `${(+h).toFixed(1)}h`); diff --git a/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx b/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx index 5e9be6e0..e0ec2c2b 100644 --- a/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx +++ b/roadmap-planner/frontend/src/components/TeamAnalyticsDashboard.jsx @@ -67,13 +67,18 @@ const METRIC_OPTIONS = [ { val: 'points', label: 'Story pts' }, ]; -// CSS-variable colors so light/dark/Atlas all resolve at paint time. +// Metric colors. Theme tokens don't work here for the same reason they +// don't for pillars — in the default light theme --accent and --ocean +// both resolve to blue, so the PRs-merged and PRs-opened lines on the +// Throughput trend (and the Opened/Merged bars on Inflow vs outflow) +// rendered identically. Absolute hex values from d3's schemeCategory10 +// guarantee five distinct hues across light / dark / Atlas modes. const METRIC_COLOR = { - prs: 'var(--accent)', - opened: 'var(--ocean)', - reviews: 'var(--amber)', - jira: 'var(--forest)', - points: 'var(--crimson)', + prs: '#1F77B4', // blue + opened: '#9467BD', // purple + reviews: '#FF7F0E', // orange + jira: '#2CA02C', // green + points: '#D62728', // red }; // Categorical palette for pillar bands. Theme semantic tokens fall short @@ -90,13 +95,13 @@ const PILLAR_PALETTE = [ '#F28E2B', // orange '#59A14F', // green '#E15759', // red - '#B07AA1', // purple + '#8E44AD', // purple — darkened from Tableau's muted #B07AA1 for stronger separation from magenta/pink slots '#EDC948', // yellow '#76B7B2', // teal - '#FF9DA7', // pink + '#E377C2', // magenta — replaces Tableau pink #FF9DA7, which washed out next to orange at 0.85 opacity '#9C755F', // brown '#17BECF', // cyan - '#BAB0AC', // gray + '#7F7F7F', // gray ]; const colorForPillar = (pillarName, allPillars) => { if (!pillarName || pillarName === 'Unassigned') return 'var(--fg-faint)'; @@ -622,7 +627,7 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick
Inflow vs outflow -

Paired weekly bars: PRs opened (left, blue) vs PRs merged (right, red). This panel is intentionally fixed to opened‑vs‑merged — it answers "is the team merging what it's opening?" and the question doesn't translate to other metrics.

+

Paired weekly bars: PRs opened (left, purple) vs PRs merged (right, blue). This panel is intentionally fixed to opened‑vs‑merged — it answers "is the team merging what it's opening?" and the question doesn't translate to other metrics.

  • Opened > Merged for many weeks → queue is growing; PRs are piling up faster than they ship.
  • Merged > Opened → the team is catching up on an existing backlog, or shipping work opened earlier in the window.
  • @@ -632,14 +637,14 @@ export default function DashboardView({ rows, orderedPillarNames, onMemberClick

    Both bars respect the Window and Pillar filters but ignore the active metric selection.

-
PRs opened (blue) vs PRs merged (red) · per week
+
PRs opened (purple) vs PRs merged (blue) · per week
+ colorA={METRIC_COLOR.opened} colorB={METRIC_COLOR.prs} />