Public: anyone can read this; the crew can change it.
Renaming a board can be undone: renameBoard logs the row it overwrote #59
Check · Review · Land · Live done — Landed and live in production.
scout/tempo:scout/42-renaming-a-board-can-be-undone-renameboa → main 1 commit fork
2 files
changed +43
−2
src/db.test.ts +31
| @@ -202,3 +202,34 @@ test("every board returned by createBoard has exactly its three default columns" | ||
| 202 | 202 | expect(db.listColumns(board.id).map((c) => c.name)).toEqual(["To do", "Doing", "Done"]); |
| 203 | 203 | } |
| 204 | 204 | }); |
| 205 | ||
| 206 | test("undoing a rename restores the old name and leaves a card moved before it untouched; a second undo then reverses the move", () => { | |
| 207 | const { boardId, columnId } = setup(); | |
| 208 | const columns = db.listColumns(boardId); | |
| 209 | const card = assertCard(db.createCard(boardId, columnId, "Card")); | |
| 210 | db.moveCard(boardId, card.id, columns[1].id, 0); | |
| 211 | expect(db.getCard(boardId, card.id)!.column_id).toBe(columns[1].id); | |
| 212 | ||
| 213 | db.renameBoard(boardId, "New name"); | |
| 214 | expect(db.getBoard(boardId)!.name).toBe("New name"); | |
| 215 | ||
| 216 | expect(db.undo(boardId)).toBe(true); | |
| 217 | expect(db.getBoard(boardId)!.name).toBe("Board"); | |
| 218 | expect(db.getCard(boardId, card.id)!.column_id).toBe(columns[1].id); | |
| 219 | ||
| 220 | expect(db.undo(boardId)).toBe(true); | |
| 221 | expect(db.getCard(boardId, card.id)!.column_id).toBe(columnId); | |
| 222 | }); | |
| 223 | ||
| 224 | test("renaming a board that does not exist does nothing and logs nothing to undo", () => { | |
| 225 | expect(db.renameBoard("no-such-board", "New name")).toBe(false); | |
| 226 | }); | |
| 227 | ||
| 228 | test("an action row logged before restoreBoardName existed still undoes cleanly", () => { | |
| 229 | const { boardId, columnId } = setup(); | |
| 230 | const card = assertCard(db.createCard(boardId, columnId, "Card")); | |
| 231 | db.db.query("INSERT INTO action (board_id, payload) VALUES (?1, ?2)").run(boardId, JSON.stringify({ deleteCardIds: [card.id] })); | |
| 232 | ||
| 233 | expect(db.undo(boardId)).toBe(true); | |
| 234 | expect(db.getCard(boardId, card.id)).toBeNull(); | |
| 235 | }); |
src/db.ts +12 −2
| @@ -91,6 +91,9 @@ type Payload = { | ||
| 91 | 91 | restoreCards?: Card[]; |
| 92 | 92 | deleteColumnIds?: string[]; |
| 93 | 93 | deleteCardIds?: string[]; |
| 94 | // Only the name — restoring the whole board row would also reverse a | |
| 95 | // claim, and ownership is not undoable. | |
| 96 | restoreBoardName?: { id: string; name: string }; | |
| 94 | 97 | }; |
| 95 | 98 | |
| 96 | 99 | // Runs inside the same transaction as the write that logged the action, so |
| @@ -150,9 +153,13 @@ export function claimBoard(boardId: string, owner: { id: string; name: string }) | ||
| 150 | 153 | }).immediate(); |
| 151 | 154 | } |
| 152 | 155 | |
| 153 | export function renameBoard(boardId: string, name: string): void { | |
| 154 | db.transaction((): void => { | |
| 156 | export function renameBoard(boardId: string, name: string): boolean { | |
| 157 | return db.transaction((): boolean => { | |
| 158 | const before = getBoard(boardId); | |
| 159 | if (!before) return false; | |
| 155 | 160 | db.query("UPDATE board SET name = ?2 WHERE id = ?1").run(boardId, name); |
| 161 | logAction(boardId, { restoreBoardName: { id: boardId, name: before.name } }); | |
| 162 | return true; | |
| 156 | 163 | }).immediate(); |
| 157 | 164 | } |
| 158 | 165 | |
| @@ -355,6 +362,9 @@ export function undo(boardId: string): boolean { | ||
| 355 | 362 | for (const c of payload.restoreCards ?? []) restoreCard.run(c.id, c.board_id, c.column_id, c.title, c.position, c.created_at, c.updated_at); |
| 356 | 363 | for (const id of payload.deleteColumnIds ?? []) db.query("DELETE FROM board_column WHERE id = ?1").run(id); |
| 357 | 364 | for (const id of payload.deleteCardIds ?? []) db.query("DELETE FROM card WHERE id = ?1").run(id); |
| 365 | if (payload.restoreBoardName) { | |
| 366 | db.query("UPDATE board SET name = ?2 WHERE id = ?1").run(payload.restoreBoardName.id, payload.restoreBoardName.name); | |
| 367 | } | |
| 358 | 368 | db.query("DELETE FROM action WHERE id = ?1").run(row.id); |
| 359 | 369 | return true; |
| 360 | 370 | }).immediate(); |
new above
offline