From 9f5d2f6488473519bd1f67c46c3e09b0f329ba99 Mon Sep 17 00:00:00 2001 From: jubnl <66769052+jubnl@users.noreply.github.com> Date: Tue, 16 Jun 2026 08:39:39 +0200 Subject: [PATCH] fix(planner): scroll long place description/notes on mobile (#1195) (#1199) The place details card (PlaceInspector) clipped long description/notes with no way to scroll. The content area is a flex column whose children (description/notes) had the default flex-shrink: 1, so once the card hit its maxHeight cap they compressed to fit and their overflow:hidden clipped the text instead of overflowing into a scroll region. - Make the content area a bounded scroll region (flex: 1 1 auto, minHeight: 0, overflowY: auto, momentum + overscroll containment). - Pin description/notes with flexShrink: 0 so they keep natural height and the card overflows into the scroll instead of clipping. - Pin header/footer with flexShrink: 0 so they stay fixed while scrolling. - Add wordBreak/overflowWrap to the description div to fix horizontal clip. --- .../Planner/PlaceInspector.test.tsx | 38 +++++++++++++++++++ .../src/components/Planner/PlaceInspector.tsx | 10 ++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/client/src/components/Planner/PlaceInspector.test.tsx b/client/src/components/Planner/PlaceInspector.test.tsx index 1c35fd0b..22e51a0d 100644 --- a/client/src/components/Planner/PlaceInspector.test.tsx +++ b/client/src/components/Planner/PlaceInspector.test.tsx @@ -647,5 +647,43 @@ describe('PlaceInspector', () => { expect(screen.queryByText('Participants')).toBeNull(); }); + // ── Scroll / overflow (issue #1195) ────────────────────────────────────── + + it('FE-PLANNER-INSPECTOR-046: content area is a bounded flex scroll region', () => { + const longText = 'Lorem ipsum dolor sit amet. '.repeat(200); + const p = buildPlace({ id: 200, description: longText, notes: longText } as any); + render(); + const scroll = screen.getByTestId('inspector-scroll') as HTMLElement; + expect(scroll.style.overflowY).toBe('auto'); + expect(scroll.style.minHeight).toBe('0px'); + // flex must allow the region to shrink/grow within the capped card + expect(scroll.style.flex).not.toBe(''); + expect(scroll.style.flex).not.toBe('0 0 auto'); + }); + + it('FE-PLANNER-INSPECTOR-047: long unbroken description wraps instead of clipping horizontally', () => { + const longWord = 'https://example.com/' + 'a'.repeat(300); + const p = buildPlace({ id: 201, description: longWord } as any); + const { container } = render(); + const descDiv = container.querySelector('.collab-note-md') as HTMLElement; + expect(descDiv).toBeTruthy(); + expect(descDiv.style.overflowWrap).toBe('anywhere'); + expect(descDiv.style.wordBreak).toBe('break-word'); + }); + + it('FE-PLANNER-INSPECTOR-048: description/notes do not shrink so the card scrolls instead of clipping', () => { + const longText = 'Lorem ipsum dolor sit amet. '.repeat(200); + const p = buildPlace({ id: 202, description: longText, notes: longText } as any); + const { container } = render(); + const notes = Array.from(container.querySelectorAll('.collab-note-md')) as HTMLElement[]; + // Both description and notes containers must keep their natural height + // (flex-shrink: 0) — otherwise they compress inside the flex column and + // overflow:hidden clips the text with no scroll (issue #1195). + expect(notes.length).toBe(2); + for (const el of notes) { + expect(el.style.flexShrink).toBe('0'); + } + }); + }); diff --git a/client/src/components/Planner/PlaceInspector.tsx b/client/src/components/Planner/PlaceInspector.tsx index ab4f6ddb..30c93fc6 100644 --- a/client/src/components/Planner/PlaceInspector.tsx +++ b/client/src/components/Planner/PlaceInspector.tsx @@ -217,7 +217,7 @@ export default function PlaceInspector({ locale={locale} timeFormat={timeFormat} onClose={onClose} /> {/* Content — scrollable */} -
+
{/* Info-Chips — hidden on mobile, shown on desktop */}
@@ -253,14 +253,14 @@ export default function PlaceInspector({ {/* Description / Summary */} {(place.description || googleDetails?.summary) && ( -
+
{place.description || googleDetails?.summary || ''}
)} {/* Notes */} {place.notes && ( -
+
{place.notes}
)} @@ -279,7 +279,7 @@ export default function PlaceInspector({
{/* Footer actions */} -
+
{selectedDayId && ( assignmentInDay ? ( onRemoveAssignment(selectedDayId, assignmentInDay.id)} variant="ghost" icon={} @@ -497,7 +497,7 @@ function ParticipantsBox({ tripMembers, participantIds, allJoined, onSetParticip function PlaceInspectorHeader({ openNow, place, category, t, editingName, nameInputRef, nameValue, setNameValue, commitNameEdit, handleNameKeyDown, startNameEdit, onUpdatePlace, locale, timeFormat, onClose }: any) { return ( -
+
{/* Avatar with open/closed ring + tag */}