Studio: harden the profile stats aggregation

Four fixes from review:

- _as_float raised OverflowError on a JSON integer wider than float, so
  one oversized counter returned 500 for the whole panel. It degrades to
  zero now, like every other unreadable field.
- Fork dedup elected one winner per source thread, but fork_chat_thread
  copies a single parent_id branch, so sibling forks of a retry and a
  regeneration hold different rows. Electing per original message keeps
  each branch when the source is deleted.
- create_run claims a resume source before the continuation logs its
  first step, so a continuation that failed early took the source's
  completed steps and tokens with it. Supersession now needs the
  continuation to have reached the source's step.
- Cumulative activity is a running total over the displayed window, but
  a narrow card trims older weeks without rebasing, opening the first
  visible bar at the hidden total and flattening the rest.
This commit is contained in:
Unsloth 2026-07-29 02:34:24 -07:00
commit df7300a3e1
5 changed files with 218 additions and 27 deletions

View file

@ -12,6 +12,7 @@ import {
heatLevel,
parseDayKey,
seriesForMode,
windowBaseline,
} from "../../utils/stats-format";
import { StatsCard } from "./stat-primitives";
@ -39,6 +40,7 @@ function buildColumns(
daily: ProfileStatsDay[],
values: number[],
columns: number,
mode: ActivityMode,
): Cell[][] {
if (daily.length === 0 || columns <= 0) return [];
@ -52,6 +54,9 @@ function buildColumns(
const capacity = columns * DAYS_PER_WEEK - trailing;
const start = Math.max(0, daily.length - capacity);
const visible = daily.slice(start);
// Cumulative is a running total over what the grid shows, so a narrow card
// that drops older weeks has to rebase off the last hidden day.
const baseline = windowBaseline(values, start, mode);
const cells: Cell[] = [];
// Pad so every column is a Monday-started week.
@ -62,7 +67,11 @@ function buildColumns(
cells.push({ key: `pad-${index}`, day: null, value: 0 });
}
for (const [index, day] of visible.entries()) {
cells.push({ key: day.date, day, value: values[start + index] ?? 0 });
cells.push({
key: day.date,
day,
value: (values[start + index] ?? 0) - baseline,
});
}
const grid: Cell[][] = [];
@ -245,8 +254,8 @@ export function TokenActivityCard({ daily }: { daily: ProfileStatsDay[] }) {
const shaded = mode === "daily";
const values = useMemo(() => seriesForMode(daily, mode), [daily, mode]);
const grid = useMemo(
() => buildColumns(daily, values, columns),
[daily, values, columns],
() => buildColumns(daily, values, columns, mode),
[daily, values, columns, mode],
);
// The app language, not the browser's: those differ whenever the user picks
// a language in Settings, and it has to be a dependency so switching while

View file

@ -103,6 +103,20 @@ export type ActivityMode = "daily" | "weekly" | "cumulative";
* since the backend caps the series and seeding it with everything older would
* flatten every bar against a baseline the grid cannot show.
*/
/**
* What to subtract from a cumulative series once the grid drops older days.
* Without it the first visible bar opens at the hidden total and the whole
* window flattens against a baseline the user cannot see.
*/
export function windowBaseline(
values: number[],
start: number,
mode: ActivityMode,
): number {
if (mode !== "cumulative" || start <= 0) return 0;
return values[start - 1] ?? 0;
}
export function seriesForMode(
daily: Array<{ date: string; tokens: number }>,
mode: ActivityMode,