## Implementation Plan: Image Paste into Issue / Comment Textareas ### Objective Add the ability for users to paste images (Ctrl+V / Cmd+V) directly from the clipboard into the issue description textarea and all comment textareas. The image is uploaded via the existing `POST /upload` endpoint and inserted as markdown `![](url)` at the cursor position. ### Files to Modify #### 1. `static/js/IssueReporter.js` - **Add a new method** `_onPaste(event)` to the `IssueReporter` class (or as a module function). - **Logic**: ```javascript _onPaste(event) { const items = event.clipboardData?.items; if (!items) return; for (const item of items) { if (item.kind === 'file' && item.type.startsWith('image/')) { event.preventDefault(); // stop default paste (plain text) const file = item.getAsFile(); this._uploadImage(file, event.target); break; } } } _uploadImage(file, textarea) { // 1. Show a small loading indicator (optional) inside the textarea or as a toast. // 2. Use `Http.sendForm()` or `fetch()` to POST to /upload // Form data: `file` with key from server (likely "file") // 3. On success, get `{ uid, url, ... }`. // 4. Call TextInput.insertAtCursor(textarea, `![](url)`). // 5. Dispatch `input` event on textarea (TextInput already does this). // 6. On error, show error toast. } ``` - **Where to bind**: In the `init()` or constructor, after the textarea element is known: ```javascript this.textarea?.addEventListener('paste', this._onPaste.bind(this)); ``` - **Textarea selector**: `document.querySelector('#issue-description')` – already present in the file. #### 2. `static/js/CommentManager.js` - **Same pattern** as `IssueReporter.js`. Add a `_onPaste` method and bind it to the comment textareas. - **Textarea selectors**: - For issue detail page: `textarea[name='body']` (inside `#issue-detail`). - For site-wide comment form: `textarea[name='content']` (inside `._comment-form`). - **Binding location**: In the method that initialises the comment form (e.g., `init()` or after AJAX load). Iterate over all matching textareas and attach the listener. #### 3. `static/js/TextInput.js` – No changes needed The existing `TextInput.insertAtCursor(element, text)` handles cursor position restoration and `input` event dispatch. We will reuse it. #### 4. Backend (no changes) - `routers/uploads.py` – already accepts `POST /upload` with a `file` field. - `models.py` – `IssueForm` and `IssueCommentForm` already have `attachment_uids`. - `routers/issues/attachments.py` – linking logic intact. #### 5. Templates (no changes) All target textareas already have stable class/id and name attributes. ### Testing (New Tests Required) Because no paste tests exist, we will add a Python integration test that simulates a paste-like upload (multipart file upload) and verifies the returned UID is usable. This is a minimal addition that exercises the upload pathway that will be triggered by paste. - **File**: `tests/api/uploads/test_paste_upload.py` - **Content**: Upload an image file via HTTP POST to `/upload` with the same form key used by the frontend. Verify response contains `uid` and `url`. This test already exists in `tests/api/uploads/upload.py` but we add a specific test name to document paste coverage. - Also ensure the existing `tests/api/issues/create.py` and `tests/api/issues/attachments.py` pass unchanged – they already test attachment linking. ### Implementation Steps (Agent Execution Order) 1. Add paste handler to `static/js/IssueReporter.js` (about 25 lines). 2. Add paste handler to `static/js/CommentManager.js` (about 30 lines, allowing for multiple textareas). 3. Add a new test file `tests/api/uploads/test_paste_upload.py` (or append to `upload.py`) with a simple test that uploads a PNG and verifies response shape. 4. Run `pip install -e '.[dev]' -q && make test-unit` – verify all tests pass. 5. Run `pip install -q ruff && ruff check .` – no new lint errors. 6. Commit all changes with a descriptive message. ### Definition of Done - [ ] All changes are committed to the repository and `git diff --stat` shows only the intended file modifications. - [ ] The project's test command passes: ``` pip install -e '.[dev]' -q && make test-unit ``` (All existing Python unit tests pass; the new upload test also passes.) - [ ] The project's lint command passes: ``` pip install -q ruff && ruff check . ``` (No Python lint errors.) - [ ] Manual/QA verification (for a human after deployment): Pasting a PNG/JPG from clipboard into the issue description textarea inserts `![](/uploads/...)` at cursor position and the rendered image appears after save. - [ ] Same behaviour confirmed for comment textareas on issue detail page and site-wide comment form. No backend or template changes were required, and the total new JavaScript code is under 60 lines.