From 8296ffc3db1fad04750603e84d322b2b1dfd98e1 Mon Sep 17 00:00:00 2001 From: ChuckBuilds Date: Thu, 16 Jul 2026 17:04:38 -0400 Subject: [PATCH] fix(web): custom-feed logo upload uses the wrong request/response contract Every custom-feed logo upload has been failing: handleCustomFeedLogoUpload posts the file under field name "file" and reads the response from data.data.files, but the backend endpoint it calls (api_v3.upload_plugin_asset, /api/v3/plugins/assets/upload) requires the field name "files" (checks 'files' not in request.files, 400s "No files provided" otherwise) and returns the result in a top-level "uploaded_files" key - there is no nested "data" wrapper in the response at all. Confirmed by reading the endpoint directly, and cross-checked against file-upload-single.js, a sibling widget that uses the correct contract against the same endpoint. - formData.append('file', file) -> formData.append('files', file) - data.data.files / data.data.files[0] -> data.uploaded_files / data.uploaded_files[0] No other call sites in this file used the stale contract (grepped for both patterns after the fix - zero remaining). The response entries' 'path' and 'id' fields (both read further down in the same handler) are unaffected - only the wrapper shape was wrong. Found incidentally while re-verifying a CodeRabbit review on an unrelated PR (#417) that had deleted a differently-named dead file (custom-feeds-helpers.js) with the same bug; this widget (custom-feeds.js) is the live code path and was never touched by that PR. Validation: brace/paren balance check; explicit assertions that the old field name and response shape no longer appear anywhere in the file. No Python changed, no existing tests cover this endpoint's client flow. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ --- web_interface/static/v3/js/widgets/custom-feeds.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/web_interface/static/v3/js/widgets/custom-feeds.js b/web_interface/static/v3/js/widgets/custom-feeds.js index 62ed412b..7f52d335 100644 --- a/web_interface/static/v3/js/widgets/custom-feeds.js +++ b/web_interface/static/v3/js/widgets/custom-feeds.js @@ -404,9 +404,15 @@ if (!file) return; const formData = new FormData(); - formData.append('file', file); + // Backend contract (see api_v3.upload_plugin_asset): the request + // field must be named "files" (it does request.files.getlist('files') + // and 400s with "No files provided" otherwise), and the response + // carries the result in a top-level "uploaded_files" key, not nested + // under "data". file-upload-single.js's working upload flow uses this + // same contract. + formData.append('files', file); formData.append('plugin_id', pluginId); - + fetch('/api/v3/plugins/assets/upload', { method: 'POST', body: formData @@ -421,8 +427,8 @@ return response.json(); }) .then(data => { - if (data.status === 'success' && data.data && data.data.files && data.data.files.length > 0) { - const uploadedFile = data.data.files[0]; + if (data.status === 'success' && data.uploaded_files && data.uploaded_files.length > 0) { + const uploadedFile = data.uploaded_files[0]; const row = document.querySelector(`#${fieldId}_tbody tr[data-index="${index}"]`); if (row) { const logoCell = row.querySelector('td:nth-child(3)');