This site uses one functional cookie to keep feature rollouts consistent for you. Nothing is set until you choose. See the privacy notice.
Dev notes
/to-do started as a read-only page over a table I seeded with a script. It rendered what was outstanding and let me tick things off, which sounds like a to-do list and is not quite one: everything on it had to be put there by me running SQL. The gap that actually mattered was not a feature, it was that noting something down cost more than the thought was worth, so things stayed in my head instead.
Items sit in phases, ordered by a position column, and the ordering is most of what the page is for. The obvious implementation reads the current maximum, adds one, and writes it back. Two adds arriving together both read the same maximum and both claim the same slot.
So the position is computed inside the insert rather than around it:
INSERT INTO todos (project, phase, title, detail, position) SELECT $1, $2, $3, $4, COALESCE(MAX(position), 0) + 1 FROM todos WHERE phase = $2 RETURNING *
One statement, so there is no window between reading and writing. The client never sends a position and the create schema rejects the field outright rather than ignoring it, because a silently dropped field looks like it worked from the caller's side.
The maximum deliberately counts soft-deleted rows. Skipping them would reuse a position that a removed item still holds, and then restoring anything later would collide with whatever took its place.
Removing sets deleted_at rather than deleting the row, and every read filters it out. That decision came from the same place as an earlier one on this page: the tick is checkbox-only, because a label wrapping the whole row turned every stray click while reading into a state change. The remove control lives next to that checkbox, so it gets a confirm step and a delete that can be undone.
The guard is the part worth copying:
UPDATE todos SET deleted_at = NOW(), updated_at = NOW() WHERE id = $1 AND deleted_at IS NULL RETURNING *
Without AND deleted_at IS NULL a second delete succeeds quietly and moves the timestamp. With it, the second one matches nothing and answers 404, which is the honest response. Ticking a row that has been removed is refused for the same reason.
There is no deleted_by column. It is a single-owner table behind an email allowlist, and an actor column would imply a multi-user model that nothing enforces.
An added item appears immediately and is reconciled when the server answers. The failure path is where the interesting bug lives: the obvious rollback drops the last item in the list, which is the right row exactly until two adds are in flight, and then it is the other person's work. So the insert carries a temporary id and the rollback filters on that id specifically.
The other half is smaller and matters more in practice. A failed add keeps the text in the box and says what happened. Losing a sentence you just typed because the network blinked is the kind of small betrayal that stops a tool being used, and the input is only cleared once the write has really landed.
Testing that honestly took a change of approach. Asserting the item appears proves nothing when the mocked refetch would put it there anyway, so the test holds the POST open and never resolves it. Anything on screen after that got there optimistically, which is the actual claim.
Both repositories are public, and the list is a record of what has not been fixed yet. Committing it to either one would publish the gaps to anyone reading the source, so the rows live in Postgres behind the same allowlist as the page.
The awkward consequence is that the list is the one thing about this project I cannot hand to a coding agent to read, which is a trade I would make again. The interesting part is that the gate is worth nothing to anyone holding the database credential directly, which is its own write-up.