Fixing a typo meant deleting the task and typing it again, which also cost
you its position in the list. Click the text to edit it. Enter saves,
Escape cancels, clicking away saves.
The rename rule lives in tasks.js and is tested. It refuses blank input
rather than treating it as a delete, since selecting all and hitting enter
by accident should give you your task back, and it returns the original
array untouched when nothing changed so an idle edit does not write to
storage.
The interesting part was what editing collides with. Keystrokes are
stopped from reaching the row, or Delete would remove the task you are
typing into and Alt+Arrow would reorder it mid-edit. A drag cannot start
from inside the field, and the click a drag leaves behind is swallowed, or
dropping a row would open it for editing. Switching day, deferring a task
and the midnight rollover all close an open editor, so it cannot be left
hanging over a row that is no longer there.
The app is built around two days and had no way to move anything between
them. "I won't get to this today" is the most obvious thing someone wants
from a two-day list, and the only route was deleting the task and typing
it again somewhere else.
Each row gets an arrow next to delete. On Today it sends the task forward,
on Tomorrow it points back, so one control covers both directions and the
label says which one you are getting. Alt+Right and Alt+Left do the same
from the keyboard, and each only acts in the direction that makes sense
from the day you are on.
The move itself is in src/lib/defer.js with tests, since it touches two
date keys at once and the failure modes are worth pinning down: a missing
id, the same day twice, an empty destination, and a task that somehow
already exists on the other side.
A pass over desktop, tablet and phone widths, measuring rather than
eyeballing.
The one that matters: a task could not be deleted on a phone. The delete
button is revealed on hover, touch devices report hover: none, so it sat
at zero opacity with nothing to reveal it. The README told people to hover
over a task and click it. It is now always visible where hover does not
exist.
Touch targets were under the 44px minimum: the checkbox at 24, delete at
32, the footer links at 23 tall. They now get 44px hit areas from overlays
that leave the drawn size alone, behind a pointer: coarse query so a mouse
keeps small precise targets and the full row stays draggable.
Two contrast failures in light mode. The date was the brand blue on the
near-white background at 3.54:1, so text now uses a darker --accent-text
and reads 5.14:1. Completed rows carried opacity 0.8 over an already
mid-contrast blue, which came to 3.34:1; the tint, the strikethrough and
the colour say "done" well enough without it, and dropping it gives 4.78:1.
Both themes now pass AA everywhere measured.
The checkbox sat vertically centred, so on a task wrapping to five lines
it floated in the middle of the block. Rows align to the top and task text
gets a 24px line box, matching the checkbox exactly, which also spaces
wrapped lines better.
Reduced motion was only honoured by the empty-state drawing. Svelte's
transitions run in JavaScript and never saw the media query, so tasks
still flew in on a stagger. The component reads the preference directly
and collapses its durations, and CSS covers the rest. The lift on a
dragged card stays, since it tracks the pointer rather than playing at you.
Long unbroken words already wrapped and no breakpoint scrolled sideways.
Two things that were deliberately left out of 2.0.0.
Export writes every stored day to a JSON file. Import reads one back, and
only ever adds: a task whose id is already there is left alone, so
importing the same file twice does nothing and importing into a list
you're using can't lose work. That is also why it needs no confirmation
dialog. The trade is that import restores rather than reverts.
Both sit in a quiet line under the task list rather than the header, since
they get used about twice a year and the header is what you look at all
day.
For the PWA half, the service worker is about fifty lines with no
dependency, because the strategy falls out of how Vite builds. Documents
go network first and fall back to cache, so a deploy is picked up as soon
as you're online and nobody ends up stuck on an old build. Fingerprinted
assets go cache first and are kept, since their names change when their
contents do. No build-time asset manifest needed.
Icons are SVG in the manifest, which stays sharp at any size and costs
about a kilobyte, plus one 180px PNG because iOS wants a raster
apple-touch-icon. Two theme-color metas so the phone status bar follows
the theme, and safe-area padding on the header, without which the header
sits under the clock once installed on an iPhone.
Verified against the production build: worker registers and claims the
page, shell and assets land in cache, and with the server stopped the app
still loads, adds a task and persists it.
Removes docs/superpowers, which held a design note and an implementation
plan. Those were working notes rather than anything the project needs, and
they don't belong in the repo.
Everything else here is wording: em dashes swapped for ordinary
punctuation across the README and the code comments, and a few sentences
straightened out.
The README described migration that never ran, and listed Backspace as a
delete key.
It now says what actually happens: unfinished work carries across gaps,
completed tasks are cleared with their day, storage prunes itself, and old
keys convert on first open. Adds Cmd/Ctrl+Z and Alt+Arrow, the touch long
press, the screenshot's new path, and how to run the tests.