Let a press of Undo be taken back: a board that can redo what it just reversed.
Why. Undo on a shared board is a blunt instrument — it reverses the last change anyone made, and today there is no way back. A person who presses it once too often (or presses it at the same moment as a colleague) has destroyed work with a control that was meant to protect it, and the only remedy is retyping. #118 gives every action a label and undo() a return value; that is the piece redo needs, because a redo stack without names is unusable on a board two people share.
Done looks like
[ ] undo(boardId) pushes what it reversed onto a redo stack rather than deleting the action row outright, and redo(boardId) replays it — restoring the rows undo removed and removing the rows undo restored — in one immediate() transaction, bumping the revision and publishing one event, exactly like every other write.
[ ] The stack is per board, bounded by a named constant in src/limits.ts (the undo depth is 200; the redo depth can be much smaller), and it is cleared by any new change to the board, because a redo after someone else has edited is no longer the change it claims to be. That rule is a test, not a comment.
[ ] redo returns the same string | null label shape #118 gives undo, and db.peekRedo(boardId) gives the page the label of what Redo would replay.
[ ] POST /b/:id/redo sits beside /undo with the same same-origin guard, the same rate-limit spend, the same three answer shapes, and a Redo button beside Undo that is disabled with "Nothing to redo" when the stack is empty.
[ ] The terminal gets the same key beside its u, and its help line says so.
[ ] Tests that fail without the change: undo then redo restores the board exactly (same ids, positions, timestamps); a write between them empties the redo stack and the button disables; redo on an empty stack is a no-op that answers like every other "nothing to do"; the label round-trips; a second tab sees the redo arrive live.
[ ] The README's undo paragraph becomes an undo/redo paragraph, including the "any new change clears it" rule.
Files I expect to change
src/db.ts, src/routes.ts, src/pages/board.ts, src/keys.ts and src/tty.ts, src/db.test.ts, src/server.test.ts, README.md.
Risks
Waits for #118: it changes what an action row holds and what undo() returns, and this builds directly on both.
#93 (undo takes back your own last change) changes which row undo picks. Redo must follow the same rule — a person redoes their own undo — so read #93 before choosing where the stack lives, and if #93 has not landed, keep redo board-wide and say in the pull that it follows undo wherever it goes.
A redo row can aim at a column or card that a later change deleted. That is the same restore-into-a-gap hazard #93 already names; answer it the same way and test it.
Suggested: scout, reviewed by juniper. Waits for #118.
Let a press of Undo be taken back: a board that can redo what it just reversed.
Why. Undo on a shared board is a blunt instrument — it reverses the last change anyone made, and today there is no way back. A person who presses it once too often (or presses it at the same moment as a colleague) has destroyed work with a control that was meant to protect it, and the only remedy is retyping. #118 gives every action a label and
undo()a return value; that is the piece redo needs, because a redo stack without names is unusable on a board two people share.Done looks like
undo(boardId)pushes what it reversed onto a redo stack rather than deleting the action row outright, andredo(boardId)replays it — restoring the rows undo removed and removing the rows undo restored — in oneimmediate()transaction, bumping the revision and publishing one event, exactly like every other write.src/limits.ts(the undo depth is 200; the redo depth can be much smaller), and it is cleared by any new change to the board, because a redo after someone else has edited is no longer the change it claims to be. That rule is a test, not a comment.redoreturns the samestring | nulllabel shape #118 givesundo, anddb.peekRedo(boardId)gives the page the label of what Redo would replay.POST /b/:id/redosits beside/undowith the same same-origin guard, the same rate-limit spend, the same three answer shapes, and a Redo button beside Undo that isdisabledwith "Nothing to redo" when the stack is empty.u, and its help line says so.Files I expect to change
src/db.ts,src/routes.ts,src/pages/board.ts,src/keys.tsandsrc/tty.ts,src/db.test.ts,src/server.test.ts,README.md.Risks
undo()returns, and this builds directly on both.Suggested: scout, reviewed by juniper. Waits for #118.