From 551021a2c7e4af6ef4207eb92db877a4a01c2b15 Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sun, 10 May 2026 12:54:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(uploads):=20reject=20HEIC/HEIF/AVIF;=20sid?= =?UTF-8?q?ebar=20brand=20=E2=86=92=20dashboard=20link;=20activity=20tabs?= =?UTF-8?q?=20scroll?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small follow-ups: 1. HEIC/HEIF/AVIF uploads now rejected at the door ---------------------------------------------------- Symptom: an iPhone-shot image uploaded fine but came through on WhatsApp without a thumbnail. Bot logs: failed to obtain extra info heif: Error while loading plugin: Support for this compression format has not been built in Cause: the bot container's Sharp ships without a HEIF/AVIF decoder, so the thumbnail-extraction step Baileys runs throws and the message is sent without a preview. Fix: the upload validator (`validateForWhatsApp`) now rejects the HEIF family before the file ever reaches the action body. Error message: "Images are not supported, please re-upload images". New tests in `lib/whatsapp-media.test.ts`: - `isUnsupportedImageMime` recognises image/heic, image/heif, image/heic-sequence, image/avif (case-insensitive). - `isUnsupportedImageMime` does NOT flag jpeg/png/webp/gif. - `validateForWhatsApp` rejects a HEIC upload regardless of size, even below the 5 MB image cap. 2. Desktop sidebar brand is now a link to / ---------------------------------------------------- The mobile header brand pill was already a link to /; the desktop sidebar version was a static
, so clicking the "cm WhatsApp Bot" header in the sidebar did nothing. Wrapped in with `aria-label="Go to dashboard"` and a hover background to make the affordance obvious. 3. Activity tab strip switched from full-width to scrollable ---------------------------------------------------- The activity page has six tabs (All / Success / Partial / Failed / Skipped / Archived) — packing them into a `w-full` row at h-8 left every label squeezed to ~50px on mobile. Wrapped the in an `overflow-x-auto` scroller (with negative horizontal margins so the strip extends to the page edges and the first/last tabs aren't clipped) so each tab keeps a comfortable touch target on phones; on desktop the row fits naturally and no scroll bar appears. Reminders page kept its full-width layout — only 4 tabs there, they don't crowd. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/web/src/app/activity/page.tsx | 29 ++++++++++++++-------- apps/web/src/components/app-shell.tsx | 10 +++++--- apps/web/src/lib/whatsapp-media.test.ts | 32 +++++++++++++++++++++++++ apps/web/src/lib/whatsapp-media.ts | 31 ++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 13 deletions(-) diff --git a/apps/web/src/app/activity/page.tsx b/apps/web/src/app/activity/page.tsx index 01572c8..db18c37 100644 --- a/apps/web/src/app/activity/page.tsx +++ b/apps/web/src/app/activity/page.tsx @@ -215,17 +215,26 @@ export default async function ActivityPage({ searchParams }: PageProps) { )}
+ {/* Six tabs (All / Success / Partial / Failed / Skipped / Archived) + packed into a phone-width row left every label squeezed to + ~50px. Wrap the list in an overflow-x scroller so each tab + keeps a readable label + comfortable touch target on mobile; + on desktop the row fits naturally and no scroll bar appears. + Negative margins extend the scroller to the page edges so the + first/last tabs don't look clipped against the container. */} - - {FILTER_TABS.map(({ value, label }) => ( - - {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} - - {label} - - - ))} - +
+ + {FILTER_TABS.map(({ value, label }) => ( + + {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} + + {label} + + + ))} + +
{filtered.length > 0 ? ( diff --git a/apps/web/src/components/app-shell.tsx b/apps/web/src/components/app-shell.tsx index bd1c50d..945b410 100644 --- a/apps/web/src/components/app-shell.tsx +++ b/apps/web/src/components/app-shell.tsx @@ -131,8 +131,12 @@ function Sidebar() { return (