Let users throw a concept away #228
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "delete-a-concept"
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?
Closes #221.
A concept could be created, edited and sent, but never thrown away. It only disappeared by being sent or scheduled, so changing your mind left you stuck with it.
What this adds
The thread reply bar deliberately has no button. It got one during development and it was removed after looking at it: a red trash can between the formatting controls reads as a formatting action. Instead, emptying a reply cleans the concept up - the same ten-second delayed delete, so both routes carry one guarantee. That one is silent: the user is mid-edit and did not ask for a message, and typing again cancels the timer, which is the undo. It also closes the source of the "Leeg concept" rows that used to accumulate.
Why a delayed delete instead of a confirmation dialog
Deleting a draft server-side also reaps its attachment files from disk (
_delete_orphan_refs), so an undo that restored the row afterwards could not bring the files back -_file_entries_from_refsfails on a missing ref. Rather than confirm up front or promise an undo that silently loses attachments, the DELETE is held for ten seconds behind an undo toast. Nothing has happened yet, so there is nothing to restore.This reuses the shape of
delayedSend.ts, which already holds sends for ten seconds. That file's timer/toast/undo core is extracted intolib/delayedAction.tsand shared. One difference is deliberate:delayedSendwarns on tab close, because a lost send is a lost message; a lost discard just leaves the concept in place, so discard does not warn.ReplyComposerruns both directions through one watcher: content means the concept lives (which also takes back a discard started from the Concepten list, inside its window), empty means clean it up. A second guard stops the server seed from re-loading a concept that is already on its way out. Without either, the timer would delete what the user just wrote and reap its files.Toast.vue's fixedwidth: 360pxbecame a floor: the action row isflex-shrink: 0, so the message got 155px while "Concept weggegooid" needs 158, and wrapped to two lines over three pixels.Components
No custom CSS.
nldd-button/nldd-icon-buttonwithvariant="critical-transparent"andicon="trash"; NLDD'stextsupplies both the aria-label and the tooltip.critical-transparentrather thandestructiveso the least-wanted action on screen does not outshout Verstuur. The one new rule is a flex spacer in the list row.Review found real bugs
An adversarial review pass caught seven defects that the passing tests did not, all fixed in
201c3f8. The sharpest:close()bumps the composer's session, so no adoption guard could ever match: no delete, no toast, concept silently left behind. The create now hands back a promise instead of being polled for. The test that "covered" this passed on an unreachable path because it never mirroredApp.vueunsettingopen; it now does, and fails without the fix.Verified
679 frontend tests, 1657 backend tests, typecheck, build, eslint and pre-commit all green. Backend untouched: both DELETE endpoints already existed for the internal send cleanup.
Every new test was mutation-checked - the protection removed, the test confirmed to fail, the code restored. Driven in the running app too: the row vanishes at once, the toast undoes it, ten seconds later the concept is really gone (confirmed across a reload), and the trash icon renders a real glyph rather than the empty box an unknown icon name would give.