Skip to content

Commit cc4e583

Browse files
committed
Make sub-mode chips work in velocity mode
Derive prod/test velocity from consecutive days' cumulative values. All three sub-modes now work: - All: per-language with prod/test split - Prod vs test: two aggregate stacks - Languages only: per-language activity
1 parent a886461 commit cc4e583

3 files changed

Lines changed: 114 additions & 36 deletions

File tree

src/app.css

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -426,12 +426,6 @@ body::before {
426426
color: #fff;
427427
}
428428

429-
.strata-chip:disabled {
430-
opacity: 0.4;
431-
cursor: default;
432-
pointer-events: none;
433-
}
434-
435429
/* Underline tab (for primary chart tabs) */
436430
.strata-tab {
437431
display: inline-flex;

src/lib/CLAUDE.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,16 @@ a Web Worker.
9393
## Components
9494

9595
- `components/ResultsChart.svelte` — Chart.js stacked area chart with a two-row toolbar. Row 1 has primary tabs
96-
(Languages / Contributors) using `strata-tab` + a Velocity toggle chip. Row 2 has sub-mode chips (disabled when
97-
velocity is on since velocity has no prod/test split): Languages tab shows All / Prod vs test / Languages only;
98-
Contributors tab shows All contributors / Top 10. A `buildContributorDatasets` function creates stacked areas per
99-
contributor (using the same mineral color palette by rank), with `computeVisibleContributors` filtering by >= 5% of
100-
total (all-contributors mode) or top 10. Contributor values are clamped to `Math.max(0, ...)`. The Contributors tab is
101-
disabled when `DayStats.contributors` data is unavailable. Velocity mode (`buildVelocityDatasets`) supports two
102-
rendering paths: when new per-language/contributor velocity fields (`languageAdded`, `languageRemoved`,
103-
`contributorAdded`, `contributorRemoved`) are available, it shows colored stacked area charts with raw daily values
104-
(activity = `Math.max(added, removed)` per language/contributor); when those fields are absent (old cached data), it
105-
falls back to a monochrome daily change line dataset. The Y-axis uses `stacked: true` + `beginAtZero: true` for
96+
(Languages / Contributors) using `strata-tab` + a Velocity toggle chip. Row 2 has sub-mode chips that work in both
97+
cumulative and velocity modes: Languages tab shows All / Prod vs test / Languages only; Contributors tab shows All
98+
contributors / Top 10. A `buildContributorDatasets` function creates stacked areas per contributor (using the same
99+
mineral color palette by rank), with `computeVisibleContributors` filtering by >= 5% of total (all-contributors mode)
100+
or top 10. Contributor values are clamped to `Math.max(0, ...)`. The Contributors tab is disabled when
101+
`DayStats.contributors` data is unavailable. Velocity mode (`buildVelocityDatasets`) respects language sub-modes:
102+
"All" splits per-language velocity into prod/test layers (derived from consecutive cumulative values), "Prod vs test"
103+
shows two aggregate stacks (Production/Test velocity), and "Languages only" shows per-language activity
104+
(`Math.max(added, removed)`). When velocity fields (`languageAdded`, `languageRemoved`) are absent (old cached data),
105+
it falls back to a monochrome daily change line dataset. The Y-axis uses `stacked: true` + `beginAtZero: true` for
106106
stacked velocity, and `stacked: false` for monochrome fallback velocity. An "Era markers" toggle (persisted to
107107
localStorage under `gitstrata-era-markers`) controls the `eraMarkersPlugin`. Pattern fills are hidden when velocity is
108108
enabled or contributors tab is active.

src/lib/components/ResultsChart.svelte

Lines changed: 104 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -487,30 +487,117 @@
487487
}
488488
489489
// Colored stacked velocity: languages tab
490-
// Note: 'prod-vs-test' sub-mode is treated as 'languages-only' — no prod/test velocity split
491490
if (tab === 'languages') {
491+
if (langSubMode === 'prod-vs-test') {
492+
// Two stacks: Production velocity and Test velocity (derived from cumulative data)
493+
const prodDeltas = daysList.map((d, idx) => {
494+
let prod = 0
495+
for (const lc of Object.values(d.languages)) prod += lc.prod ?? lc.total
496+
if (idx === 0) return prod
497+
let prevProd = 0
498+
for (const lc of Object.values(daysList[idx - 1].languages)) prevProd += lc.prod ?? lc.total
499+
return Math.abs(prod - prevProd)
500+
})
501+
const testDeltas = daysList.map((d, idx) => {
502+
let test = 0
503+
for (const lc of Object.values(d.languages)) test += lc.test ?? 0
504+
if (idx === 0) return test
505+
let prevTest = 0
506+
for (const lc of Object.values(daysList[idx - 1].languages)) prevTest += lc.test ?? 0
507+
return Math.abs(test - prevTest)
508+
})
509+
return [
510+
{
511+
label: 'Production',
512+
data: prodDeltas,
513+
backgroundColor: getChartColor(0) + '80',
514+
borderColor: getChartColor(0),
515+
borderWidth: 1.5,
516+
fill: 'origin',
517+
tension: 0.3,
518+
pointRadius: 0,
519+
pointHitRadius: 6,
520+
},
521+
{
522+
label: 'Test',
523+
data: testDeltas,
524+
backgroundColor: getChartTint(0) + '80',
525+
borderColor: getChartTint(0),
526+
borderWidth: 1.5,
527+
fill: '-1',
528+
tension: 0.3,
529+
pointRadius: 0,
530+
pointHitRadius: 6,
531+
},
532+
]
533+
}
534+
535+
// 'all' and 'languages-only' modes
492536
const { shown, other: hasOther } = computeVisibleLanguages(daysList, detectedLanguages)
493537
const datasets: ChartDataset<'line'>[] = []
494538
495539
for (let i = 0; i < shown.length; i++) {
496540
const langId = shown[i]
497541
const colorIdx = i % maxChartColors
498-
const rawActivity = daysList.map((d) => {
499-
const added = d.languageAdded?.[langId] ?? 0
500-
const removed = d.languageRemoved?.[langId] ?? 0
501-
return Math.max(added, removed)
502-
})
503-
datasets.push({
504-
label: langName(langId),
505-
data: rawActivity,
506-
backgroundColor: getChartColor(colorIdx) + '80',
507-
borderColor: getChartColor(colorIdx),
508-
borderWidth: 1.5,
509-
fill: datasets.length === 0 ? 'origin' : '-1',
510-
tension: 0.3,
511-
pointRadius: 0,
512-
pointHitRadius: 6,
513-
})
542+
const splitTest = langSubMode === 'all' && shouldSplitTestLayer(daysList, langId)
543+
544+
if (splitTest) {
545+
// Prod velocity derived from cumulative prod data
546+
const prodDeltas = daysList.map((d, idx) => {
547+
const lc = d.languages[langId]
548+
const prod = lc?.prod ?? lc?.total ?? 0
549+
if (idx === 0) return prod
550+
const prevLc = daysList[idx - 1].languages[langId]
551+
const prevProd = prevLc?.prod ?? prevLc?.total ?? 0
552+
return Math.abs(prod - prevProd)
553+
})
554+
datasets.push({
555+
label: `${langName(langId)} (prod)`,
556+
data: prodDeltas,
557+
backgroundColor: getChartColor(colorIdx) + '80',
558+
borderColor: getChartColor(colorIdx),
559+
borderWidth: 1.5,
560+
fill: datasets.length === 0 ? 'origin' : '-1',
561+
tension: 0.3,
562+
pointRadius: 0,
563+
pointHitRadius: 6,
564+
})
565+
// Test velocity derived from cumulative test data
566+
const testDeltas = daysList.map((d, idx) => {
567+
const test = d.languages[langId]?.test ?? 0
568+
if (idx === 0) return test
569+
const prevTest = daysList[idx - 1].languages[langId]?.test ?? 0
570+
return Math.abs(test - prevTest)
571+
})
572+
datasets.push({
573+
label: `${langName(langId)} (test)`,
574+
data: testDeltas,
575+
backgroundColor: getChartTint(colorIdx) + '80',
576+
borderColor: getChartTint(colorIdx),
577+
borderWidth: 1.5,
578+
fill: datasets.length === 0 ? 'origin' : '-1',
579+
tension: 0.3,
580+
pointRadius: 0,
581+
pointHitRadius: 6,
582+
})
583+
} else {
584+
const rawActivity = daysList.map((d) => {
585+
const added = d.languageAdded?.[langId] ?? 0
586+
const removed = d.languageRemoved?.[langId] ?? 0
587+
return Math.max(added, removed)
588+
})
589+
datasets.push({
590+
label: langName(langId),
591+
data: rawActivity,
592+
backgroundColor: getChartColor(colorIdx) + '80',
593+
borderColor: getChartColor(colorIdx),
594+
borderWidth: 1.5,
595+
fill: datasets.length === 0 ? 'origin' : '-1',
596+
tension: 0.3,
597+
pointRadius: 0,
598+
pointHitRadius: 6,
599+
})
600+
}
514601
}
515602
516603
if (hasOther) {
@@ -933,19 +1020,16 @@
9331020
<button
9341021
onclick={() => (languageSubMode = 'all')}
9351022
aria-pressed={languageSubMode === 'all'}
936-
disabled={velocityEnabled}
9371023
class="strata-chip">All</button
9381024
>
9391025
<button
9401026
onclick={() => (languageSubMode = 'prod-vs-test')}
9411027
aria-pressed={languageSubMode === 'prod-vs-test'}
942-
disabled={velocityEnabled}
9431028
class="strata-chip">Prod vs test</button
9441029
>
9451030
<button
9461031
onclick={() => (languageSubMode = 'languages-only')}
9471032
aria-pressed={languageSubMode === 'languages-only'}
948-
disabled={velocityEnabled}
9491033
class="strata-chip">Languages only</button
9501034
>
9511035
{:else}

0 commit comments

Comments
 (0)