78 lines
3.6 KiB
Markdown
78 lines
3.6 KiB
Markdown
---
|
||
id: ML-169.10
|
||
title: "Fix: Async optimistic UI for index/list delete streams"
|
||
status: To Do
|
||
assignee: []
|
||
created_date: "2026-05-19 11:48"
|
||
updated_date: "2026-05-20 09:41"
|
||
labels:
|
||
- perf
|
||
- fix
|
||
- ux
|
||
dependencies: []
|
||
references:
|
||
- >-
|
||
backlog/documents/doc-27 -
|
||
Phase-4-Performance-Audit-Low-Hanging-Fruit-Findings.md
|
||
modified_files:
|
||
- lib/music_library_web/live_helpers/index_actions.ex
|
||
- lib/music_library_web/live/collection_live/index.ex
|
||
- lib/music_library_web/live/wishlist_live/index.ex
|
||
- lib/music_library_web/live/scrobbled_tracks_live/index.ex
|
||
parent_task_id: ML-169
|
||
priority: medium
|
||
ordinal: 31000
|
||
---
|
||
|
||
## Description
|
||
|
||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||
|
||
Add optimistic UI for delete operations only where the item is rendered from a LiveView stream on an index/list page. The item should be removed from the stream immediately, while the database delete runs through LiveView-owned async handling so failures can be reported and rolled back.
|
||
|
||
## Problem
|
||
|
||
Index/list delete handlers currently wait for the database delete before updating the UI. `stream_delete` is used after the delete succeeds, so the interaction can feel slower than necessary.
|
||
|
||
This task is intentionally scoped to stream-backed list views only:
|
||
|
||
- Collection index and wishlist index record streams via `IndexActions.handle_delete/2`
|
||
- Scrobbled tracks index stream
|
||
|
||
Show-page deletes are out of scope. `CollectionLive.Show` and `WishlistLive.Show` navigate away after delete and do not have a list stream to optimistically update. Their failure and rollback behavior should remain synchronous unless a separate navigation-specific design is created.
|
||
|
||
## Files
|
||
|
||
- `lib/music_library_web/live_helpers/index_actions.ex` — remove record from `:records` immediately and start a LiveView async delete operation
|
||
- `lib/music_library_web/live/collection_live/index.ex` — handle async record delete success/failure for collection index
|
||
- `lib/music_library_web/live/wishlist_live/index.ex` — handle async record delete success/failure for wishlist index
|
||
- `lib/music_library_web/live/scrobbled_tracks_live/index.ex` — remove track from `:tracks` immediately and handle async track delete success/failure
|
||
|
||
## Fix
|
||
|
||
Use LiveView-owned async handling (`start_async/3` plus `handle_async/3`, or an equivalent pattern that sends results back to the LiveView process). Do not use bare `Task.start/1`, because it cannot update the socket, reinsert the stream item, or show an error toast.
|
||
|
||
Expected behavior:
|
||
|
||
1. Fetch the item needed for rollback.
|
||
2. Immediately call `stream_delete/3` on the relevant stream.
|
||
3. Start an async delete operation owned by the LiveView.
|
||
4. On success, leave the stream as-is and optionally broadcast/refresh any cross-view state the existing design requires.
|
||
5. On failure or async exit, reinsert the original item into the stream and show a user-facing error toast.
|
||
|
||
For error text, use the project’s existing user-facing error conventions (`ErrorMessages.friendly_message/1` where an error reason is shown).
|
||
|
||
<!-- SECTION:DESCRIPTION:END -->
|
||
|
||
## Acceptance Criteria
|
||
|
||
<!-- AC:BEGIN -->
|
||
|
||
- [ ] #1 Index/list delete operations remove the item from the relevant stream before waiting for the database delete
|
||
- [ ] #2 Database delete still executes through LiveView-owned async handling, not bare `Task.start/1`
|
||
- [ ] #3 Async delete failure or exit reinserts the original item into the stream and shows an error toast
|
||
- [ ] #4 Existing delete confirmation prompts still work correctly
|
||
- [ ] #5 Show-page delete handlers are not changed as part of this task
|
||
- [ ] #6 Collection and wishlist index delete behavior remains shared through `IndexActions` where practical
|
||
<!-- AC:END -->
|