mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
fix(workouts): order the list newest-first; show the year on past-year dates
The canonical picker re-sorts its output to startedAt ASC for its rollup callers, so the list route sliced the oldest page and recent workouts never surfaced on a long history — re-establish newest-first after the pick. And a workout from a previous year now shows its year so it is not read as this year's.
This commit is contained in:
1 parent
546c5c7d8f
commit
05f80ad378
7 files changed
+48
-3
No files matched your search
@@ -2,6 +2,14 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.29.4] — 2026-07-17
|
||||
|
||||
- **The workouts list shows the most recent first.** The list was ordered
|
||||
oldest-first, so on a longer history the newest sessions never made the first
|
||||
page. It now leads with the latest workout.
|
||||
- **Past-year dates show the year.** A workout from a previous year now carries
|
||||
its year in the list, so a December date is never mistaken for this year's.
|
||||
|
||||
## [1.29.3] — 2026-07-17
|
||||
|
||||
- **Hydration and micronutrients now have a home.** Your fluid intake and the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: HealthLog API
|
||||
version: 1.29.3
|
||||
version: 1.29.4
|
||||
description: >-
|
||||
Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "healthlog",
|
||||
"version": "1.29.3",
|
||||
"version": "1.29.4",
|
||||
"description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.",
|
||||
"license": "PolyForm-Noncommercial-1.0.0",
|
||||
"homepage": "https://healthlog.dev",
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ try {
|
||||
// v1.4.38.4 → v1.4.42. Do not hand-edit; bump `package.json` and rebuild.
|
||||
const CACHE_VERSION =
|
||||
(typeof self !== "undefined" && self.__APP_VERSION__) ||
|
||||
/* @sw-version-fallback */ "v1.29.3";
|
||||
/* @sw-version-fallback */ "v1.29.4";
|
||||
const STATIC_CACHE = `healthlog-static-${CACHE_VERSION}`;
|
||||
const PAGE_CACHE = `healthlog-pages-${CACHE_VERSION}`;
|
||||
// v1.18.6 — read-only data cache for a curated allowlist of safe GET `/api/*`
|
||||
|
||||
@@ -147,6 +147,28 @@ describe("GET /api/workouts — canonical dedup", () => {
|
||||
expect(body.data.meta.droppedDuplicates).toBe(1);
|
||||
});
|
||||
|
||||
it("returns the most recent workouts first", async () => {
|
||||
// The canonical picker re-sorts its output to startedAt ASC (its rollup
|
||||
// contract), so the list route must re-establish newest-first AFTER the
|
||||
// pick — otherwise offset/limit slice the OLDEST page and recent workouts
|
||||
// never surface. These two never cluster (different day + slot).
|
||||
const older = makeRow("w-old", "APPLE_HEALTH", "2026-01-01T07:00:00Z");
|
||||
const newer = makeRow("w-new", "APPLE_HEALTH", "2026-07-01T07:00:00Z");
|
||||
vi.mocked(prisma.workout.findMany).mockResolvedValueOnce([
|
||||
newer,
|
||||
older,
|
||||
] as never);
|
||||
|
||||
const res = await GET(makeRequest());
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(body.data.workouts.map((w: { id: string }) => w.id)).toEqual([
|
||||
"w-new",
|
||||
"w-old",
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns 401 without a session", async () => {
|
||||
vi.mocked(getSession).mockResolvedValueOnce(null);
|
||||
const res = await GET(makeRequest());
|
||||
|
||||
@@ -143,6 +143,17 @@ async function buildWorkoutsResponse(
|
||||
userRow?.sourcePriorityJson ?? null,
|
||||
);
|
||||
|
||||
// `pickCanonicalWorkoutRows` re-sorts its output to `startedAt ASC` (its
|
||||
// documented contract for the rollup callers). This surface is a
|
||||
// most-recent-first list, so the DB `orderBy startedAt desc` must be
|
||||
// re-established AFTER the pick — otherwise `offset`/`limit` slice the
|
||||
// OLDEST page and a user with a long history never sees recent workouts.
|
||||
canonical.sort((a, b) => {
|
||||
const delta = (b.startedAt?.getTime() ?? 0) - (a.startedAt?.getTime() ?? 0);
|
||||
if (delta !== 0) return delta;
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
|
||||
const page = canonical.slice(offset, offset + limit).map((row) => ({
|
||||
id: row.id,
|
||||
sportType: row.sportType,
|
||||
|
||||
@@ -87,10 +87,14 @@ function formatDate(
|
||||
timeFormat: TimeFormatPreference,
|
||||
): string {
|
||||
const d = new Date(iso);
|
||||
// Show the year whenever the workout is not from the current year, so a
|
||||
// "Do., 16. Dez." from a previous year is never mistaken for this year's.
|
||||
const showYear = d.getFullYear() !== new Date().getFullYear();
|
||||
return getDateTimeFormat(locale, {
|
||||
weekday: "short",
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
...(showYear ? { year: "numeric" as const } : {}),
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
...hourCycleOptions(timeFormat),
|
||||
|
||||
Reference in new issue
Block a user