feat: let the reader mark an event as repeating daily
Dailiness was read off the source's wording alone, which is wrong in both directions: a grind that resets every day but whose page never prints "daily" got no checklist, and a banner whose blurb mentions "daily login rewards" got one nobody could dismiss. The reader's answer now wins. The control sits exactly where the checklist goes — the one place the answer visibly matters — so marking an event and ticking today off are the same gesture in the same place. An override is stored only when it disagrees with detection. Recording agreement would freeze today's guess into the reader's own data, so a later parser improvement could never reach that event. Marked events also join today's dailies at the top of the page, beside the per-game chores: at 23:50 a login campaign and a commission run are the same job, and ticking one should not mean opening a sheet to find its checklist. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6177633abc
commit
7f384eb9db
@@ -9,6 +9,12 @@ export type Status = "doing" | "done";
|
||||
export interface Progress {
|
||||
status?: Status | undefined;
|
||||
effort?: Effort | undefined;
|
||||
/**
|
||||
* The reader's own answer to "does this repeat daily?", overriding what the
|
||||
* source's wording implies. Absent means they have not said and detection
|
||||
* stands — see resolveDaily in shared/daily.ts.
|
||||
*/
|
||||
daily?: boolean | undefined;
|
||||
/** Anything the reader wants to remember about it. */
|
||||
note?: string | undefined;
|
||||
at: string;
|
||||
@@ -42,6 +48,20 @@ function load(): ProgressMap {
|
||||
return seeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nothing recorded, so not worth a row. Every field the reader can set has to
|
||||
* be listed here: one left out means an event they marked *only* with that
|
||||
* field gets silently dropped on the next write.
|
||||
*/
|
||||
function isEmpty(p: Progress): boolean {
|
||||
return (
|
||||
p.status === undefined &&
|
||||
p.effort === undefined &&
|
||||
p.daily === undefined &&
|
||||
(p.note ?? "") === ""
|
||||
);
|
||||
}
|
||||
|
||||
export function useProgress() {
|
||||
const [progress, setProgress] = useState<ProgressMap>(load);
|
||||
|
||||
@@ -58,11 +78,7 @@ export function useProgress() {
|
||||
};
|
||||
// An entry with nothing recorded is not worth keeping; drop it so the
|
||||
// store stays a set of things the reader actually said something about.
|
||||
if (
|
||||
merged.status === undefined &&
|
||||
merged.effort === undefined &&
|
||||
(merged.note ?? "") === ""
|
||||
) {
|
||||
if (isEmpty(merged)) {
|
||||
const { [id]: _removed, ...rest } = prev;
|
||||
return rest;
|
||||
}
|
||||
@@ -88,11 +104,7 @@ export function useProgress() {
|
||||
status: next,
|
||||
at: new Date().toISOString(),
|
||||
};
|
||||
if (
|
||||
merged.status === undefined &&
|
||||
merged.effort === undefined &&
|
||||
(merged.note ?? "") === ""
|
||||
) {
|
||||
if (isEmpty(merged)) {
|
||||
const { [id]: _removed, ...rest } = prev;
|
||||
return rest;
|
||||
}
|
||||
@@ -102,6 +114,11 @@ export function useProgress() {
|
||||
[],
|
||||
);
|
||||
|
||||
const setDaily = useCallback(
|
||||
(id: string, daily: boolean | undefined) => patch(id, { daily }),
|
||||
[patch],
|
||||
);
|
||||
|
||||
const setEffort = useCallback(
|
||||
(id: string, effort: Effort | undefined) => patch(id, { effort }),
|
||||
[patch],
|
||||
@@ -125,5 +142,14 @@ export function useProgress() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { progress, patch, setStatus, cycleStatus, setEffort, setNote, merge };
|
||||
return {
|
||||
progress,
|
||||
patch,
|
||||
setStatus,
|
||||
cycleStatus,
|
||||
setDaily,
|
||||
setEffort,
|
||||
setNote,
|
||||
merge,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user