fix(tasks): a cycle chip counts its days left whether ticked or not

It printed the unticked days left, so ticking a cycle off dropped its
figure by one, and on the last day read 0d. Tasks now carry daysLeft, the
period's days from today, and the chip prints that. Found in the
whole-branch review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-16 05:46:50 +02:00
1 parent f19ef32e4a
commit 4427f6c2e5
3 files changed
+17 -2

No files matched your search

+3 -1
View File
@@ -86,6 +86,8 @@ export interface GoalStatus {
open: number | null;
/** Fewer days left than check-ins needed. The target is never lowered. */
shortfall: boolean;
/** A task's days left in its period from today, ticked or not — what a cycle's chip shows. */
daysLeft?: number | undefined;
}
/** Bounds a walk over keys when the end is unknown; matches daily.ts. */
@@ -207,7 +209,7 @@ export function goalSummary(goal: Goal | undefined): string {
/** The Dailies chip's short count. */
export function goalCount(s: GoalStatus): string {
// "cy" means nothing; the days left in a cycle say where it is.
if (s.scope === "cycle") return `${num(s.count)}/${num(s.required)} · ${s.open ?? 0}d`;
if (s.scope === "cycle") return `${num(s.count)}/${num(s.required)} · ${s.daysLeft ?? s.open ?? 0}d`;
const unit = s.scope === "week" ? " wk" : s.scope === "month" ? " mo" : "";
return `${num(s.count)}/${num(s.required)}${unit}`;
}
+2 -1
View File
@@ -87,7 +87,7 @@ export function taskStatus(input: {
return {
scope, measure: "amount", periodStart: start, required, count,
met: count >= required, needed: Math.max(0, required - count),
open: left.length, shortfall: false,
open: left.length, shortfall: false, daysLeft: left.length,
};
}
@@ -100,6 +100,7 @@ export function taskStatus(input: {
return {
scope, measure: "days", periodStart: start, required, count,
met: count >= required, needed, open, shortfall: needed > open,
daysLeft: left.length,
};
}
+12
View File
@@ -1,6 +1,7 @@
import { describe, expect, test } from "bun:test";
import { RESERVED_ID_SEGMENTS } from "../src/shared/custom.ts";
import { nextResetMs } from "../src/shared/daily.ts";
import { goalCount } from "../src/shared/goal.ts";
import { mintTaskId, TaskSchema, taskCaption, taskStatus, type Task } from "../src/shared/task.ts";
/**
@@ -171,3 +172,14 @@ describe("a task on another server's clock", () => {
expect(after.periodStart).toBe("2026-10-08");
});
});
describe("a cycle chip's days left", () => {
test("is the days left in the cycle, ticked or not", () => {
const t = task({ period: { unit: "cycle", days: 14, from: "2026-10-16" }, measure: { kind: "days", required: 1 } });
const at = (logged: string[], day: string) =>
goalCount(taskStatus({ task: t, region: "europe", now: noon(day), logged }));
expect(at([], "2026-10-20")).toBe("0/1 · 10d");
expect(at(["2026-10-20"], "2026-10-20")).toBe("1/1 · 10d");
expect(at(["2026-10-29"], "2026-10-29")).toBe("1/1 · 1d");
});
});