From 999c7a3c305325a8a37887c542b73666d3913477 Mon Sep 17 00:00:00 2001 From: wojtekpiskorz Date: Fri, 15 May 2026 11:54:37 +0200 Subject: [PATCH 1/2] fix(admin): show placeholder when media picker image is missing When a media_picker field references an image that no longer exists (e.g. after syncing DB between environments without syncing uploads), the img tag 404s and the preview container collapses to 0px height. Since Change/Remove buttons are position: absolute, they become invisible and unreachable via mouse. Fix: add onError handler + min-height on img + broken-image placeholder with accessible Change/Remove buttons. Applied to both: - BlockKitMediaPickerField.tsx (Block Kit plugin field) - ContentEditor.tsx (standalone image field) Tests: 3 new tests for broken image behavior, all 878 tests pass. --- .changeset/fix-media-picker-broken-image.md | 5 ++ .../components/BlockKitMediaPickerField.tsx | 84 +++++++++++++------ .../admin/src/components/ContentEditor.tsx | 75 +++++++++++++---- .../BlockKitMediaPickerField.test.tsx | 47 +++++++++++ 4 files changed, 170 insertions(+), 41 deletions(-) create mode 100644 .changeset/fix-media-picker-broken-image.md diff --git a/.changeset/fix-media-picker-broken-image.md b/.changeset/fix-media-picker-broken-image.md new file mode 100644 index 000000000..a6f397dba --- /dev/null +++ b/.changeset/fix-media-picker-broken-image.md @@ -0,0 +1,5 @@ +--- +"@emdash-cms/admin": patch +--- + +Fixes broken image collapsing media picker container — adds `onError` handler and fallback placeholder so Change/Remove buttons remain accessible when referenced image is missing from storage diff --git a/packages/admin/src/components/BlockKitMediaPickerField.tsx b/packages/admin/src/components/BlockKitMediaPickerField.tsx index 8ee052389..2bcd21255 100644 --- a/packages/admin/src/components/BlockKitMediaPickerField.tsx +++ b/packages/admin/src/components/BlockKitMediaPickerField.tsx @@ -1,6 +1,6 @@ import { Button } from "@cloudflare/kumo"; import { useLingui } from "@lingui/react/macro"; -import { Image as ImageIcon, X } from "@phosphor-icons/react"; +import { Image as ImageIcon, ImageBroken, X } from "@phosphor-icons/react"; import * as React from "react"; import type { MediaItem } from "../lib/api"; @@ -35,6 +35,7 @@ export function BlockKitMediaPickerField({ }: BlockKitMediaPickerFieldProps) { const { t } = useLingui(); const [pickerOpen, setPickerOpen] = React.useState(false); + const [imageBroken, setImageBroken] = React.useState(false); const url = typeof value === "string" && value.length > 0 ? value : ""; const filter = mimeTypeFilter ?? "image/"; const canPreview = isSafePreviewUrl(url); @@ -55,30 +56,65 @@ export function BlockKitMediaPickerField({
{canPreview ? ( -
- -
- - + imageBroken ? ( +
+
+ + {t`Image not found`} +
+
+ + +
-
+ ) : ( +
+ setImageBroken(true)} + /> +
+ + +
+
+ ) ) : ( - + imageBroken ? ( +
+
+ + {t`Image not found`} +
+
+ + +
-
+ ) : ( +
+ setImageBroken(true)} + /> +
+ + +
+
+ ) ) : (