fix(cuesheet): report image problems instead of failing silently

A value which is not a link was discarded on blur without any feedback:
the text stayed in the field, so it looked like it had been saved. We now
mark the field and say what is expected. Paths served by ontime itself
(eg. /user/slide.png) are now accepted, they were rejected before.

An image which cannot be loaded showed a broken icon with no explanation.
This is what a dropbox share link does, since it serves an html page
rather than the image, so it is worth naming the problem.

We also reserve the space of an image while it is loading, using the
aspect ratio of the last time we saw it. Rows are unmounted while out of
view, so without it the row collapses and grows again on the way back,
shifting the table under the user.

Smaller items in the same cell: the lazy loading attribute only added a
gate before the request (the row is only mounted when it is already close
to the viewport), the image had no alt text, and two expressions could
never run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKXcDrZoQXbJpaXiLi1aff
This commit is contained in:
Claude
2026-08-29 14:02:34 +00:00
parent 9ec12f4927
commit f3cd541ef0
6 changed files with 196 additions and 20 deletions
@@ -0,0 +1,22 @@
import { describe, expect, test } from 'vitest';
import { isValidImageSource } from '../cuesheet-table/cuesheet-table-elements/EditableImage';
describe('isValidImageSource()', () => {
test('accepts images hosted elsewhere', () => {
expect(isValidImageSource('https://example.com/image.png')).toBe(true);
expect(isValidImageSource('http://example.com/image.png')).toBe(true);
});
test('accepts images served by ontime', () => {
expect(isValidImageSource('/user/image.png')).toBe(true);
expect(isValidImageSource('/external/image.png')).toBe(true);
});
test('rejects values which would not resolve to an image', () => {
expect(isValidImageSource('www.example.com/image.png')).toBe(false);
expect(isValidImageSource('example.com/image.png')).toBe(false);
expect(isValidImageSource('user/image.png')).toBe(false);
expect(isValidImageSource('some text')).toBe(false);
});
});