Fix top skills history chart hiding skills with zero-level gaps.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-22 14:02:39 +02:00
parent 0926eaeb63
commit 0791ff88e9
2 changed files with 65 additions and 10 deletions
+8
View File
@@ -1072,6 +1072,14 @@ def skill_timeline(db_path: Path | str = DEFAULT_DB) -> dict[str, Any]:
if idx is not None:
series[key][idx] = row["level"]
for key in series:
last = 0
for i in range(n):
if series[key][i] > 0:
last = series[key][i]
elif last > 0:
series[key][i] = last
return {"snapshots": snapshots, "series": series}
+57 -10
View File
@@ -748,6 +748,7 @@ function skillLevelSeries(skillKey) {
if (!tl?.series) return null;
const values = tl.series[skillKey];
if (!values || values.length < 2) return null;
if (values.filter((v) => v > 0).length < 2) return null;
return values;
}
@@ -813,7 +814,7 @@ function setupTrendChartModal() {
});
}
function openTrendChartModal(title, snapshots, values, datasetLabel, color = "#4ade80") {
function openTrendChartModal(title, snapshots, values, datasetLabel, color = "#4ade80", skillSeries = false) {
setupTrendChartModal();
const modal = document.getElementById("inv-chart-modal");
document.getElementById("inv-chart-modal-title").textContent = title;
@@ -822,16 +823,20 @@ function openTrendChartModal(title, snapshots, values, datasetLabel, color = "#4
destroyChart("trendModal");
const bg = color === "#6c8cff" ? "rgba(108, 140, 255, 0.12)" : "rgba(74, 222, 128, 0.12)";
const points = skillSeries
? skillTimelineChartPoints(snapshots, values)
: timelineChartPoints(snapshots, values);
state.charts.trendModal = new Chart(document.getElementById("inv-chart-modal-canvas"), {
type: "line",
data: {
datasets: [{
label: datasetLabel,
data: timelineChartPoints(snapshots, values),
data: points,
borderColor: color,
backgroundColor: bg,
tension: 0.3,
tension: skillSeries ? 0 : 0.3,
fill: true,
spanGaps: false,
}],
},
options: chartOptsTime(snapshots),
@@ -849,7 +854,7 @@ function openSkillChartModal(skillKey, skillName) {
const values = skillLevelSeries(skillKey);
const tl = state.skillTimeline;
if (!values || !tl) return;
openTrendChartModal(skillName, tl.snapshots, values, t("skills.level"));
openTrendChartModal(skillName, tl.snapshots, values, t("skills.level"), "#4ade80", true);
}
function closeTrendChartModal() {
@@ -1736,12 +1741,10 @@ function renderTimelineCharts() {
const skillCanvas = document.getElementById("chart-skills");
if (!skillCanvas || !skillTl?.snapshots?.length) return;
const skillEntries = Object.entries(skillTl.series || {})
.map(([key, values]) => ({ key, values, latest: values[values.length - 1] || 0 }))
.sort((a, b) => b.latest - a.latest)
.slice(0, 5);
const skillEntries = pickTopSkillEntries(skillTl.series, 5);
const colors = ["#6c8cff", "#4ade80", "#fbbf24", "#f87171", "#a78bfa"];
const dashPatterns = [[], [6, 4], [2, 3], [8, 4, 2, 4], [4, 2]];
const skillName = (key) => {
const sk = state.data?.skills?.find((s) => s.key === key);
return sk?.name || key.replace(/_/g, " ");
@@ -1752,10 +1755,15 @@ function renderTimelineCharts() {
data: {
datasets: skillEntries.map((entry, idx) => ({
label: skillName(entry.key),
data: timelineChartPoints(skillTl.snapshots, entry.values),
data: skillTimelineChartPoints(skillTl.snapshots, entry.values),
borderColor: colors[idx % colors.length],
tension: 0.3,
borderWidth: 2,
borderDash: dashPatterns[idx % dashPatterns.length],
pointRadius: 4,
pointHoverRadius: 6,
tension: 0,
fill: false,
spanGaps: false,
})),
},
options: chartOptsTime(skillTl.snapshots),
@@ -1779,6 +1787,45 @@ function snapshotTimeMs(snapshot) {
return null;
}
function skillLevelDelta(values) {
const valid = values.filter((v) => v > 0);
if (valid.length < 2) return 0;
return Math.max(...valid) - Math.min(...valid);
}
function pickTopSkillEntries(series, limit = 5) {
const ranked = Object.entries(series || {})
.map(([key, values]) => ({
key,
values,
latest: values[values.length - 1] || 0,
delta: skillLevelDelta(values),
}))
.filter((e) => e.delta > 0)
.sort((a, b) => b.delta - a.delta || b.latest - a.latest);
const seen = new Set();
const picked = [];
for (const entry of ranked) {
const sig = entry.values.join(",");
if (seen.has(sig)) continue;
seen.add(sig);
picked.push(entry);
if (picked.length >= limit) break;
}
return picked;
}
function skillTimelineChartPoints(snapshots, values) {
return snapshots.map((snapshot, i) => {
const x = snapshotTimeMs(snapshot);
if (x == null) return null;
const y = values[i];
if (y == null || y <= 0) return null;
return { x, y };
}).filter(Boolean);
}
function timelineChartPoints(snapshots, values) {
return snapshots.map((snapshot, i) => {
const x = snapshotTimeMs(snapshot);