refactor(cuesheet): improve image scrolling - #2190
Conversation
The cuesheet is a virtualised table: rows are unmounted once they leave the viewport, taking the <img> elements with them. On the way back the image has no dimensions until the browser makes it available, so the row grows under the user as it loads. We now remember the aspect ratio of the images we have seen and use it to reserve the space the image will take. This holds two numbers per image: the image data itself is left to the browser cache, which is better placed than us to decide when memory should be released. Also drops the lazy loading attribute: the row is only mounted when it is already close to the viewport, so it only adds a gate before the request. Whether the image is fetched again on scroll back is decided by the cache headers of the host serving it, and cannot be worked around from here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKXcDrZoQXbJpaXiLi1aff
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
…oll-k26i4u' into claude/cuesheet-image-unload-scroll-k26i4u # Conflicts: # apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx
A path to a local file resolves on the machine running ontime, but not for the clients we serve the cuesheet to, so we no longer accept it. Validation now parses the value as a URL and checks the protocol, which also rejects malformed values that the previous prefix check let through. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKXcDrZoQXbJpaXiLi1aff
We were remembering the aspect ratio and reserving the full width of the column, which over-reserves for an image smaller than the column: the row would settle to a smaller size once the image was shown. We now remember the size of the image and express the reservation in CSS as min(100%, width), which is what the image itself resolves to at any column width. Column sizes are applied as CSS variables and do not re-render the cells, so the reservation follows a resize on its own. Also groups the tests around the behaviour they describe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKXcDrZoQXbJpaXiLi1aff
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🔵 Needs a closer look
There are concrete security/performance regressions to address (scheme validation for window.open, and restoring lazy-loading behavior for images).
Pull request overview
This PR refactors cuesheet image handling to reduce table layout shifting during scroll (virtualized rows) and to improve URL validation/feedback for image sources.
Changes:
- Add
isValidImageSourcevalidation (http/https) with UI feedback for rejected inputs. - Cache previously loaded image dimensions and reserve space on re-mount to prevent row-height shifts.
- Add tests for URL validation and the image-dimensions LRU cache, and pass a column label through for
alttext.
File summaries
| File | Description |
|---|---|
| apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx | Adds URL validation, load/error state, reserved-space rendering using remembered dimensions, and alt text support. |
| apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.module.scss | Adds styling for invalid input state and user-visible error/rejection messages. |
| apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/cuesheetColsFactory.tsx | Passes fieldLabel into EditableImage for accessibility (alt). |
| apps/client/src/views/cuesheet/tests/EditableImage.test.ts | Adds unit tests for isValidImageSource. |
| apps/client/src/common/utils/imageDimensions.ts | Introduces an in-memory (LRU-capped) cache for image dimensions to support reserved-space rendering. |
| apps/client/src/common/utils/tests/imageDimensions.test.ts | Adds tests for remembering dimensions and LRU eviction behavior. |
Review details
Suppressed comments (3)
apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx:81
- The validation error message says "(https://...)" but
isValidImageSourceaccepts both http and https; this is misleading for users who paste an http URL and see it accepted.
{isRejected && <span className={style.message}>Images are referenced by link (https://...)</span>}
apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx:119
- The previous implementation used
loading='lazy', but the new<img>removed it. In a table view with many images, this can cause unnecessary network/CPU work and degrade scrolling performance.
<img
src={initialValue}
alt={fieldLabel}
className={style.image}
onLoad={(event) => {
apps/client/src/views/cuesheet/cuesheet-table/cuesheet-table-elements/EditableImage.tsx:58
openInNewTabopens whatever string is stored ininitialValue; since this value can come from persisted data, it should be constrained to http(s) before callingwindow.opento avoid opening unexpected/unsupported URL schemes.
This issue also appears in the following locations of the same file:
- line 81
- line 115
const openInNewTab = () => {
if (initialValue) {
window.open(initialValue, '_blank', 'noopener,noreferrer');
}
};
- Files reviewed: 6/6 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
No description provided.