Context
Since the Gravatar → UserAvatarComponent refactor (PR #3927, commit a5f4e9ab, 2026-04-09), user avatars are rendered via setImages(config.api, user.avatar, size * 2, null) — routed through the shared setImages helper in src/lib/plugins/images.js.
On downstream projects deployed over HTTPS with an empty api.port (the standard prod setup — port 443 implicit), avatars are broken: the rendered URL contains a stray colon with no port, e.g. https://trawl.me:/api/uploads/images/abc.jpg, which browsers reject.
Repro confirmed on trawl_vue (built with DEVKIT_VUE_api_port= in CI). Affects every user with an uploaded avatar on the scraps list and anywhere else UserAvatarComponent is used. Users without uploaded avatars see the initials fallback and are unaffected.
Root cause
src/lib/plugins/images.js:17 unconditionally concatenates :${api.port}:
return \`${api.protocol}://${api.host}:${api.port}/api/uploads/images/${name}\`;
When api.port === '', this produces https://host:/api/... — malformed.
Other URL builders in the codebase already handle this correctly — e.g. trawl_vue/src/modules/scraps/utils/screenshotUrl.js:12-13:
const port = config.api.port ? \`:${config.api.port}\` : '';
return \`${config.api.protocol}://${config.api.host}${port}/${config.api.base}/uploads/${screenshot}\`;
The setImages plugin was never updated with this pattern, but the bug was latent because pre-refactor avatars went through Gravatar (remote URL, not setImages) — the refactor exposed it.
Scope
Bug affects all downstream projects built with empty api.port (i.e., HTTPS prod on standard 443). Since the refactor shipped 2026-04-09, every downstream that ran /update-stack since then has broken avatars.
Fix
1. src/lib/plugins/images.js
Replace the single return line with conditional port handling:
app.config.globalProperties.setImages = (api, file, size, operation) => {
const base = file.split('.');
if (base.length !== 2) return file;
let [name] = base;
if (size) name = \`${name}-${size}\`;
if (operation) name = \`${name}-${operation}\`;
name = \`${name}.${base[1]}\`;
const port = api.port ? \`:${api.port}\` : '';
return \`${api.protocol}://${api.host}${port}/api/uploads/images/${name}\`;
};
Single-line change in the return, plus one port local. No signature change, no API break.
2. src/lib/plugins/tests/images.unit.tests.js
Add a test case for empty port:
it('omits port when api.port is empty', () => {
const api = { protocol: 'https', host: 'trawl.me', port: '' };
expect(setImages(api, 'photo.jpg', null, null))
.toBe('https://trawl.me/api/uploads/images/photo.jpg');
});
it('omits port when api.port is undefined', () => {
const api = { protocol: 'https', host: 'trawl.me' };
expect(setImages(api, 'photo.jpg', null, null))
.toBe('https://trawl.me/api/uploads/images/photo.jpg');
});
Existing tests (which pass port: '3000') remain valid and unchanged.
3. No other files touched
UserAvatarComponent: no change — it passes config.api as-is, the fix is transparent.
MIGRATIONS.md: no entry needed (bugfix, not a breaking change).
Risks
None. Purely additive / defensive — URLs with a port continue to render identically.
Validation
/verify on the stack (lint + unit + audit)
- Manual: deploy a downstream (trawl_vue first via
/update-stack), check https://trawl.me scraps list — avatars should load instead of 404.
Downstream rollout
Once merged, each affected downstream needs /update-stack to pick up the fix: trawl_vue, pierreb_vue, comes_vue, montaine_vue (and any other HTTPS-deployed project).
Estimated cost
~10 min real work. Single PR, ~5 LOC in source + ~10 LOC in tests.
Context
Since the Gravatar →
UserAvatarComponentrefactor (PR #3927, commita5f4e9ab, 2026-04-09), user avatars are rendered viasetImages(config.api, user.avatar, size * 2, null)— routed through the sharedsetImageshelper insrc/lib/plugins/images.js.On downstream projects deployed over HTTPS with an empty
api.port(the standard prod setup — port 443 implicit), avatars are broken: the rendered URL contains a stray colon with no port, e.g.https://trawl.me:/api/uploads/images/abc.jpg, which browsers reject.Repro confirmed on trawl_vue (built with
DEVKIT_VUE_api_port=in CI). Affects every user with an uploaded avatar on the scraps list and anywhere elseUserAvatarComponentis used. Users without uploaded avatars see the initials fallback and are unaffected.Root cause
src/lib/plugins/images.js:17unconditionally concatenates:${api.port}:When
api.port === '', this produceshttps://host:/api/...— malformed.Other URL builders in the codebase already handle this correctly — e.g.
trawl_vue/src/modules/scraps/utils/screenshotUrl.js:12-13:The
setImagesplugin was never updated with this pattern, but the bug was latent because pre-refactor avatars went through Gravatar (remote URL, notsetImages) — the refactor exposed it.Scope
Bug affects all downstream projects built with empty
api.port(i.e., HTTPS prod on standard 443). Since the refactor shipped 2026-04-09, every downstream that ran/update-stacksince then has broken avatars.Fix
1.
src/lib/plugins/images.jsReplace the single return line with conditional port handling:
Single-line change in the return, plus one
portlocal. No signature change, no API break.2.
src/lib/plugins/tests/images.unit.tests.jsAdd a test case for empty port:
Existing tests (which pass
port: '3000') remain valid and unchanged.3. No other files touched
UserAvatarComponent: no change — it passesconfig.apias-is, the fix is transparent.MIGRATIONS.md: no entry needed (bugfix, not a breaking change).Risks
None. Purely additive / defensive — URLs with a port continue to render identically.
Validation
/verifyon the stack (lint + unit + audit)/update-stack), checkhttps://trawl.mescraps list — avatars should load instead of 404.Downstream rollout
Once merged, each affected downstream needs
/update-stackto pick up the fix:trawl_vue,pierreb_vue,comes_vue,montaine_vue(and any other HTTPS-deployed project).Estimated cost
~10 min real work. Single PR, ~5 LOC in source + ~10 LOC in tests.