Replaces checkAndMigrateTasks, which never ran. Its guard compared
currentDate, set from new Date() at init, against a fresh new Date() a few
milliseconds later in onMount, so it was never true. Unfinished tasks have
been sitting under old date keys ever since.
The rule now: unfinished tasks from every prior day move into today,
oldest first, ahead of whatever is already there. Completed ones are
dropped and the old key deleted. It spans gaps, which the original never
did even in theory, since it only looked one day back.
now is a parameter. That's the part that makes the boundary testable.
A factory over an injectable backend, so persistence can be tested without
a DOM.
Fixes the unguarded JSON.parse that would white-screen the app on one bad
value, and falls back to memory when localStorage itself throws (Safari
private mode, full quota).
migrateLegacyKeys converts old "Sat Aug 15 2026" keys in place. It's
idempotent, and it leaves anything it can't parse alone rather than
deleting it.
Switches task keys to ISO YYYY-MM-DD. They sort, so finding every day
before today is a string compare instead of re-parsing each key.
Two things worth knowing. fromKey builds from date parts rather than
parsing a bare ISO string, which gets read as UTC and lands on the wrong
day west of Greenwich. addDays goes through setDate, so it stays on the
same calendar day across a DST change instead of adding a flat 24 hours.
Groundwork before pulling logic out of App.svelte.
Vitest runs in a node environment. Nothing under test needs a DOM, because
every module takes its dependencies as arguments.
package-lock.json is tracked now and the Dockerfile installs with npm ci,
so the nightly rebuild can't quietly land on different versions.
.gitignore: .DS_Store was anchored to the root and missed nested copies,
and dist/ wasn't ignored at all.