SIGN IN SIGN UP

feat(front): show loading and error states for video attachment previews (#24454)

Closes #24453

## Problem

`mp4` is a previewable type, so opening a video attachment mounts the
viewer and hands the URL to `@cyntler/react-doc-viewer`, which creates
the `<video>`. From there the user gets no feedback at all:

- While the video downloads, the modal shows the empty viewer container.
On a large recording over a slow connection that lasts a long time and
reads as a broken preview rather than a loading one.
- If the media never loads — expired signed URL, unsupported codec,
network error — that empty box is the **final** state. No message, and
no way to reach the file, even though this same component already offers
a download for types it cannot preview.

## Change

`useMediaLoadStatus` tracks the media element's load state and the
viewer uses it for two new states:

- **loading** → the `Loader` is shown over the viewer container.
- **error** → the existing "Preview Not Available" panel with its
download button, instead of an empty box.

Non-video previews are untouched: the hook is disabled for them, keeps
returning `loading`, and nothing reads it.

The `Suspense` fallback that covers the lazy-loaded viewer chunk in
`AttachmentList` also gets the `Loader`, where it was a bare line of
text.

## Why the listeners are on the container

The `<video>` belongs to the viewer library, so `onLoadedData` /
`onError` cannot be passed as props. The hook attaches them to the
container in the **capture** phase instead: media events do not bubble,
but they still travel down through the ancestors, and the container is
mounted before the library creates the element, so no event is missed.

That is the one non-obvious part of the change, so it is what the test
pins down — it dispatches a non-bubbling `loadeddata` on a child
`<video>` and asserts the hook still reports `ready`.

## Tests

`useMediaLoadStatus.test.tsx` (new, 4 cases): starts in `loading`;
reports `ready` on a non-bubbling `loadeddata`; reports `error` on
`error`; stays `loading` when disabled, so non-media previews keep their
current behaviour.

`jest src/modules/activities/files` passes 5 suites / 15 tests. `oxlint
--type-aware` and `oxfmt --check` are clean on the four files, and `tsgo
--noEmit` reports no errors in them.

The behaviour was also verified by hand against a real 28-minute video
attachment on a self-hosted instance: the overlay appears while the
video downloads and is gone once `readyState` reaches 4.

## Note on scope

I kept this to the loading and error states. The viewer also letterboxes
videos — the container is a fixed tall box, so a landscape video sits at
the top with dead space under it — but that is a layout change with
visual trade-offs, so it seemed better not to bundle it here. Happy to
open it separately if you want it.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/24454?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
E
Emmanuel Hernández Bazán committed
1935c33b6414b3308c3fe577a4902b430d083ccd
Parent: bb44697
Committed by GitHub <noreply@github.com> on 8/24/2026, 10:09:00 AM