Compare commits

...
1 Commits
Author SHA1 Message Date
ChuckBuildsandClaude Sonnet 5 8296ffc3db 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
2026-07-16 17:04:38 -04:00
@@ -404,9 +404,15 @@
if (!file) return; if (!file) return;
const formData = new FormData(); 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); formData.append('plugin_id', pluginId);
fetch('/api/v3/plugins/assets/upload', { fetch('/api/v3/plugins/assets/upload', {
method: 'POST', method: 'POST',
body: formData body: formData
@@ -421,8 +427,8 @@
return response.json(); return response.json();
}) })
.then(data => { .then(data => {
if (data.status === 'success' && data.data && data.data.files && data.data.files.length > 0) { if (data.status === 'success' && data.uploaded_files && data.uploaded_files.length > 0) {
const uploadedFile = data.data.files[0]; const uploadedFile = data.uploaded_files[0];
const row = document.querySelector(`#${fieldId}_tbody tr[data-index="${index}"]`); const row = document.querySelector(`#${fieldId}_tbody tr[data-index="${index}"]`);
if (row) { if (row) {
const logoCell = row.querySelector('td:nth-child(3)'); const logoCell = row.querySelector('td:nth-child(3)');