fix(tasks): keep a task whole through import and edit, and fix two counts

Review found that import and edit rebuilt a task from this build's parse.
That stripped any field it didn't know, and import dropped a newer
version's task entirely. Both now keep the record as stored and validate
only to display it.

An event whose goal is points can't be ticked, so it no longer counts as
waiting today: it counts with its week. The month-day field reset
itself to 1 when emptied; it now keeps a draft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-13 13:54:29 +02:00
1 parent 54d8424d1e
commit 5a4c746a44
7 files changed
+110 -28

No files matched your search

+8 -4
View File
@@ -61,9 +61,11 @@ export interface DailyItem {
goal?: GoalStatus | undefined;
/**
* How often it is due. Chores and events are daily and make up the header's
* "today" count; tasks are counted by their own period beside it.
* "today" count; tasks are counted by their own period beside it. An event
* whose goal is a number has no "today" — nothing ticks — so it counts with
* its week, or, over its whole run, in no tally at all.
*/
cadence: "day" | "week" | "month";
cadence: "day" | "week" | "month" | "event";
/** A task's period, for finding the one before it in Catch up. */
period?: Period | undefined;
/** A task's unit, printed after its target. */
@@ -149,6 +151,7 @@ export function dailyGroups(
for (const event of events) {
if (event.game !== lane) continue;
const goal = goalFor(event.id);
items.push({
key: event.id,
game: lane,
@@ -158,8 +161,8 @@ export function dailyGroups(
today,
resetsIn,
notBefore: startOf(event),
goal: goalFor(event.id),
cadence: "day",
goal,
cadence: goal?.measure !== "amount" ? "day" : goal.scope === "week" ? "week" : "event",
});
}
@@ -221,6 +224,7 @@ export function dailyTally(
const month = { total: 0, complete: 0 };
for (const i of items) {
const met = i.goal?.met === true;
if (i.cadence === "event") continue;
if (i.cadence !== "day") {
const tally = i.cadence === "week" ? week : month;
tally.total += 1;
+13 -4
View File
@@ -13,23 +13,32 @@ export interface TaskFields {
title: string;
unit: "week" | "month";
weekday: number;
monthDay: number;
/** As typed, so the field can be emptied and retyped; read through `parseMonthDay`. */
monthDay: string | number;
kind: "days" | "amount";
required: string;
target: string;
amountUnit: string;
}
/** A typed day of the month, clamped to 1–31; null while empty or unreadable. */
export function parseMonthDay(raw: string): number | null {
const n = Number.parseInt(raw, 10);
return Number.isFinite(n) ? Math.min(31, Math.max(1, n)) : null;
}
/**
* The task these fields describe, or null while they do not describe one the
* store would accept. Checked against `TaskSchema` itself, so the form can
* never offer Save on something the store would then refuse.
*/
export function taskDraftFrom(f: TaskFields): TaskDraft | null {
const monthDay = parseMonthDay(String(f.monthDay));
if (f.unit === "month" && monthDay === null) return null;
const period =
f.unit === "week"
? ({ unit: "week", startsOn: f.weekday } as const)
: ({ unit: "month", startsOn: f.monthDay } as const);
: ({ unit: "month", startsOn: monthDay ?? 1 } as const);
const unit = f.amountUnit.trim();
const target = parseAmount(f.target);
const measure =
@@ -78,7 +87,7 @@ export function TaskForm({
title: initial?.title ?? "",
unit: initial?.period.unit ?? "week",
weekday: initial?.period.unit === "week" ? initial.period.startsOn : 1,
monthDay: initial?.period.unit === "month" ? initial.period.startsOn : 1,
monthDay: String(initial?.period.unit === "month" ? initial.period.startsOn : 1),
kind: initial?.measure.kind ?? "days",
required: initial?.measure.kind === "days" ? String(initial.measure.required) : "1",
target: initial?.measure.kind === "amount" ? String(initial.measure.target) : "",
@@ -156,7 +165,7 @@ export function TaskForm({
min={1}
max={31}
value={f.monthDay}
onChange={(e) => set({ monthDay: Number.parseInt(e.target.value, 10) || 1 })}
onChange={(e) => set({ monthDay: e.target.value })}
className={`tnum w-14 ${field}`}
/>
</label>