Deleting a board has a thirty-day undo; home and settings polish #215
No reviewers
Labels
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente!215
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "issue-196-soft-delete"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #196: the soft delete from the design posted there. The super-admin half (moderation delete, bans, the board record page) follows in its own PR, stacked on this one. This PR also carries the QA polish on the home page and the board settings from the same pass.
Deleting a board is undoable for 30 days
Board.DeletedAt, plus who deleted it and whether it was moderation, and changes nothing else.BoardAccess.VisibleTogains a first clause, so a deleted board drops out of every read at once: home, switcher, both calendars and.icsfeeds, search, the for-you strip, the move menu, link previews and the launch resume.BoardNotEmptyExceptionis gone: a board with cards can be deleted, because nothing is destroyed at the moment of pressing.DeletedBoardState("Deleted on …", until when, Restore for its admins, a way home for everyone) instead of "not found". A board deleted as moderation isn't restorable by its own admins; that side is in the moderation PR.BoardPurge, a daily hosted service, destroys boards deleted more thanBoardService.GraceDays(30) ago. It's the one place that may destroy a card: the foreign keys stayRestrict, andPurgeExpiredAsyncwrites the delete order by hand in one transaction.AddBoardSoftDelete: three nullable columns, nothing to backfill.Home and settings polish
ponenteEntrance.playHome, gated ondata-home-enteredlike the board and the calendar): headings lead their sections, for-you cards rise, tiles pop and their mini boards fill column by column, a pinned board's pin wiggles. The strip and the tiles play side by side, all within ~0.8 s, and nothing plays under reduced motion. The cards rise rather than slide sideways because a sideways transform inside a scroll container shifted the strip's scroll width mid-play.PersistentComponentState), so the switcher is never empty while the circuit connects. That was the flakyaccess.specswitcher test. Disposal no longer calls JS during prerender, which removes 117 logged errors per e2e run.Changelog
The two unreleased lines about board admins and board settings merge into one (the block was at its cap), plus one line for the undo.
Tests
BoardAccessTests: a deleted board is visible to nobody, andWasVisibleToanswers exactly whatVisibleTodid before the delete.🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
✅ Verdict: approve — no blocking issues found.
The soft delete is built the right way round: one clause in
BoardAccess.VisibleTotakes a deleted board out of every read, the delete itself destroys nothing, andPurgeExpiredAsyncis the single hand-written, transactional exception that may touch a card. Permission checks are in place on both delete and restore (RequireAdminister), the moderation flag is stored rather than inferred, and the deleted/denied/missing distinction inResolveAsynconly discloses a board's deletion to somebody who could see it before. ThePersistentComponentStatecarry inNavHeaderis scoped to the carried records, not the entities, and the prerender disposal guard is correct. Migration, snapshot and context configuration agree; the e2e helpers and the once-only entrance counter test are coherent.Two non-blocking remarks:
RestoreAsync(andDeleteAsync) return silently when the board is already in the requested state, so a race between two admins restoring shows the "Board restored" toast for a board that was never gone. Harmless, but aDeletedAt is nullcheck before the success toast would be tidier.DeletedBoardStatecaches_canRestoreper board id, so a grant changed while the page sits open (or a viewer whose session changes) is not picked up until the component is rebuilt. Fine in practice for an empty state.⏱ 3m 29s · 9 steps · $0.0335 · tokens: 122.9k in / 8.8k out (+355.1k cache-read) · Go: 5h 37% · wk 21% · mo 43%
📜 Full session transcript