|
5 | 5 | interface Props { |
6 | 6 | days: DayStats[] |
7 | 7 | detectedLanguages: string[] |
8 | | - repoSizeBytes?: number | null |
9 | 8 | onHighlightDate?: (date: string | null) => void |
10 | 9 | } |
11 | 10 |
|
12 | | - let { days, repoSizeBytes, onHighlightDate }: Props = $props() |
| 11 | + let { days, onHighlightDate }: Props = $props() |
13 | 12 |
|
14 | | - const formattedRepoSize = $derived.by(() => { |
15 | | - if (repoSizeBytes == null || repoSizeBytes <= 0) return null |
16 | | - return `${(repoSizeBytes / (1024 * 1024)).toFixed(1)} MB` |
17 | | - }) |
18 | | -
|
19 | | - // --- Total lines --- |
| 13 | + // --- Total size --- |
20 | 14 | const totalLines = $derived(days.length > 0 ? days[days.length - 1].total : 0) |
21 | 15 |
|
22 | 16 | const formattedTotal = $derived(totalLines.toLocaleString('en-US').replace(/,/g, '\u2009')) |
23 | 17 |
|
| 18 | + /** Count commits from days that have actual commits (not gap-filled) */ |
| 19 | + const totalCommits = $derived( |
| 20 | + days |
| 21 | + .filter((d) => d.comments.length === 0 || d.comments[0] !== '-') |
| 22 | + .reduce((sum, d) => sum + d.comments.length, 0), |
| 23 | + ) |
| 24 | +
|
| 25 | + const formattedCommits = $derived(totalCommits.toLocaleString('en-US').replace(/,/g, '\u2009')) |
| 26 | +
|
24 | 27 | // --- Prod / test split --- |
25 | 28 | const prodTestSplit = $derived.by(() => { |
26 | 29 | if (days.length === 0) return { prod: 0, test: 0, prodPct: 0, testPct: 0 } |
|
73 | 76 | return Math.round((last.total - first.total) / daysBetween) |
74 | 77 | }) |
75 | 78 |
|
76 | | - const avgGrowthLast90 = $derived.by(() => { |
| 79 | + const computeAvgGrowthForLastNDays = (n: number): number => { |
77 | 80 | if (days.length < 2) return 0 |
78 | 81 | const lastDate = new Date(days[days.length - 1].date) |
79 | | - const cutoffStr = new Date(lastDate.getTime() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10) |
| 82 | + const cutoffStr = new Date(lastDate.getTime() - n * 24 * 60 * 60 * 1000).toISOString().slice(0, 10) |
80 | 83 |
|
81 | 84 | // Find the first day at or after the cutoff |
82 | 85 | let startIdx = 0 |
|
94 | 97 | (new Date(endDay.date).getTime() - new Date(startDay.date).getTime()) / (1000 * 60 * 60 * 24), |
95 | 98 | ) |
96 | 99 | return Math.round((endDay.total - startDay.total) / daysBetween) |
97 | | - }) |
| 100 | + } |
98 | 101 |
|
99 | | - /** Show arrow only when the difference is significant (>20%) */ |
| 102 | + const avgGrowthLast90 = $derived(computeAvgGrowthForLastNDays(90)) |
| 103 | + const avgGrowthLast30 = $derived(computeAvgGrowthForLastNDays(30)) |
| 104 | +
|
| 105 | + /** Show arrow only when the 30d growth differs significantly (>20%) from the 90d growth */ |
100 | 106 | const growthTrend = $derived.by(() => { |
101 | | - if (avgDailyGrowth === 0 && avgGrowthLast90 === 0) return 'neutral' as const |
102 | | - const base = Math.max(Math.abs(avgDailyGrowth), 1) |
103 | | - const ratio = Math.abs(avgGrowthLast90 - avgDailyGrowth) / base |
| 107 | + if (avgGrowthLast90 === 0 && avgGrowthLast30 === 0) return 'neutral' as const |
| 108 | + const base = Math.max(Math.abs(avgGrowthLast90), 1) |
| 109 | + const ratio = Math.abs(avgGrowthLast30 - avgGrowthLast90) / base |
104 | 110 | if (ratio <= 0.2) return 'neutral' as const |
105 | | - return avgGrowthLast90 > avgDailyGrowth ? ('up' as const) : ('down' as const) |
| 111 | + return avgGrowthLast30 > avgGrowthLast90 ? ('up' as const) : ('down' as const) |
106 | 112 | }) |
107 | 113 |
|
108 | 114 | // --- Age --- |
|
260 | 266 | role="region" |
261 | 267 | aria-label="Summary statistics" |
262 | 268 | > |
263 | | - <!-- Total lines --> |
| 269 | + <!-- Total size --> |
264 | 270 | <div class="strata-card p-4"> |
265 | 271 | <p |
266 | 272 | class="text-foreground-tertiary" |
267 | 273 | style="font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.05em; text-transform: uppercase;" |
268 | 274 | > |
269 | | - Total lines |
| 275 | + Total size |
270 | 276 | </p> |
271 | 277 | <p |
272 | 278 | class="mt-2 text-foreground" |
273 | 279 | style="font-family: var(--font-mono); font-size: 1.125rem; font-weight: 500; letter-spacing: -0.01em;" |
274 | 280 | > |
275 | | - {formattedTotal} |
| 281 | + LoC: {formattedTotal} |
| 282 | + </p> |
| 283 | + <p class="mt-1 text-foreground-secondary" style="font-family: var(--font-mono); font-size: 0.75rem;"> |
| 284 | + {totalCommits === 1 ? 'commit' : 'commits'}: {formattedCommits} |
276 | 285 | </p> |
277 | | - {#if formattedRepoSize} |
278 | | - <p |
279 | | - class="mt-1 text-foreground-secondary" |
280 | | - style="font-family: var(--font-mono); font-size: 0.75rem;" |
281 | | - title="It includes history" |
282 | | - > |
283 | | - Repo size: {formattedRepoSize} |
284 | | - </p> |
285 | | - {/if} |
286 | 286 | </div> |
287 | 287 |
|
288 | 288 | <!-- Prod / test split --> |
|
355 | 355 | class="text-foreground-secondary" |
356 | 356 | style="font-family: var(--font-mono); font-size: 0.75rem; white-space: nowrap;" |
357 | 357 | > |
358 | | - Last 90d: {avgGrowthLast90 >= 0 ? '+' : ''}{formatNumber(avgGrowthLast90)}/day |
| 358 | + Last 90d: {avgGrowthLast90 >= 0 ? '+' : ''}{formatNumber(avgGrowthLast90)}/d, 30d: {avgGrowthLast30 >= 0 |
| 359 | + ? '+' |
| 360 | + : ''}{formatNumber(avgGrowthLast30)}/d |
359 | 361 | </p> |
360 | 362 | {#if growthTrend !== 'neutral'} |
361 | 363 | <svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden="true" style="flex-shrink: 0;"> |
362 | 364 | <title |
363 | 365 | >{growthTrend === 'up' |
364 | | - ? 'Recent growth is higher than the average' |
365 | | - : 'Recent growth is lower than the average'}</title |
| 366 | + ? '30-day growth is higher than the 90-day average' |
| 367 | + : '30-day growth is lower than the 90-day average'}</title |
366 | 368 | > |
367 | 369 | {#if growthTrend === 'up'} |
368 | 370 | <path |
|
0 commit comments