## Implementation Plan: Add Loading Animations to Post Action Buttons ### 1. CSS – `client/css/base.css` (after line 494) Add loading-state rules for the five button classes that lack `.btn`: ```css /* Post action button loading states */ .post-action-btn.is-loading, .comment-vote-btn.is-loading, .comment-action-btn.is-loading, .comment-form-submit.is-loading, .vote-star.is-loading { pointer-events: none; } .post-action-btn.is-loading .btn-spinner, .comment-vote-btn.is-loading .btn-spinner, .comment-action-btn.is-loading .btn-spinner, .comment-form-submit.is-loading .btn-spinner, .vote-star.is-loading .btn-spinner { display: inline-block; } ``` ### 2. Templates – Add `` to each button | Template | Button class | Insert before label / content | |---|---|---| | `_bookmark_button.html` | `post-action-btn bookmark-btn` | `` | | `_comment_vote.html` (or wherever `comment-vote-btn` lives) | `comment-vote-btn` | Same | | `_comment_form.html:12` | `comment-form-submit` | Same | | `_star_vote.html` | `vote-star` | Same | If a button already contains an inline icon (e.g. bookmark icon), place the spinner **before** that icon – the spinner will be hidden by default and shown via CSS when `.is-loading` is applied. ### 3. JavaScript – `OptimisticAction.js` Modify both `submit()` and `submitOptimistic()` to accept an optional third/fourth parameter `button` (HTMLElement). - Before fetch: set `button.disabled = true` and `button.classList.add('is-loading')`. - On fetch completion (success or error): restore `button.disabled = false` and `button.classList.remove('is-loading')`. **Signature changes:** ```js // Line 7 submit(url, params, errorTarget, button = null) // In the fetch chain, add button state management. // Line 19 submitOptimistic(url, params, errorTarget, apply, revert, reconcile, button = null) ``` ### 4. JavaScript – All four `OptimisticAction` consumers Each click/prevent-default handler must capture the button element and pass it to the parent method. - **`VoteManager.js:22-38`** – `event.currentTarget` is the button; pass it. - **`BookmarkManager.js:11-27`** – same. - **`ReactionBar.js:70-80`** – same (likely `event.target.closest('button')` if emoji click targets a span). - **`PollManager.js:38-46`** – same. ### 5. JavaScript – `CommentManager.js` Add `button.classList.add('is-loading')` immediately after setting `button.disabled = true` in all three locations: - Line 26 (delete): after `button.disabled = true`, add `button.classList.add('is-loading')`. - Line 121 (edit): same after `save.disabled = true`. - Line 208 (reply form): same after `btn.disabled = true`. In each case, the existing restore code (e.g. line 41 for delete) should also remove the `is-loading` class with `button.classList.remove('is-loading')`. Ensure this is present (it is already in delete/edit; for reply form add it on error/complete). ### 6. (Optional but recommended) Add a minimal unit test In `tests/e2e/issues/create.py` the existing `test_submit_disables_button_and_shows_spinner` validates the pattern for issues. No equivalent exists for post actions – adding one is out of scope for this ticket, but the plan must at least ensure existing tests still pass. --- ## Definition of Done - [ ] All five templates (`_bookmark_button.html`, `_comment_vote.html`, `_comment_form.html`, `_star_vote.html`, plus any `comment-action-btn` template) contain `` inside the target button element. - [ ] `base.css` contains the loading-state rules from section 1. - [ ] `OptimisticAction.js` manages `disabled` and `is-loading` on an optional `button` parameter. - [ ] `VoteManager.js`, `BookmarkManager.js`, `ReactionBar.js`, `PollManager.js` pass the button element to the parent method. - [ ] `CommentManager.js` adds `is-loading` class in all three places where `disabled` is set, and removes it on completion/error. - [ ] The project’s lint command passes (`pip install -q ruff && ruff check .` – no new lint errors). - [ ] The project’s test command passes (`pip install -e '.[dev]' -q && make test-unit` – all existing unit tests remain green).