From ec06035855250e3d1e7db3f93967e0875de8e33f Mon Sep 17 00:00:00 2001 From: Maurice Date: Tue, 29 Sep 2026 20:49:56 +0200 Subject: [PATCH] fix(collab): a long link title wraps instead of being cut off The link chip held its title on one line with an ellipsis, and nothing in the UI showed the rest: the tooltip names the action, not the link, and a phone or the PWA has no hover at all. The title now wraps and the chip grows in height, the host drops to its own line under a wrapped title, and the chip keeps rounded corners instead of pill ends once it is taller. Closes #2550 --- .../components/Collab/CollabLinks.test.tsx | 28 ++++++++++++++++++- client/src/index.css | 24 +++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/client/src/components/Collab/CollabLinks.test.tsx b/client/src/components/Collab/CollabLinks.test.tsx index d7cfe20e3..1a17fe2e4 100644 --- a/client/src/components/Collab/CollabLinks.test.tsx +++ b/client/src/components/Collab/CollabLinks.test.tsx @@ -1,4 +1,4 @@ -// FE-COMP-LINKS-001 to FE-COMP-LINKS-015 +// FE-COMP-LINKS-001 to FE-COMP-LINKS-016 vi.mock('../../api/websocket', () => ({ connect: vi.fn(), @@ -223,6 +223,32 @@ describe('CollabLinks', () => { expect(action).toMatch(/margin:\s*-4px 0/); }); + it('FE-COMP-LINKS-016: a long title wraps and grows the chip instead of ending in an ellipsis (#2550)', async () => { + const title = 'Very long ferry timetable for the northern isles in the summer season'; + server.use(http.get('/api/trips/1/collab/links', () => HttpResponse.json({ links: [buildLink({ title })] }))); + render(); + // The whole name is in the chip, not a shortened copy of it. + expect(await screen.findByText(title)).toHaveClass('collab-link-chip__title'); + + // jsdom lays nothing out, so the rules are the assertion: the title may not be + // cut, and it has to be allowed to break, even inside one long word. + const css = readFileSync(resolve(process.cwd(), 'src/index.css'), 'utf8'); + const rule = (selector: string) => { + const at = css.indexOf(`\n${selector} {`); + return at === -1 ? '' : css.slice(at, css.indexOf('}', at)); + }; + const titleRule = rule('.collab-link-chip__title'); + expect(titleRule, '.collab-link-chip__title rule missing from index.css').not.toBe(''); + expect(titleRule).not.toMatch(/white-space:\s*nowrap/); + expect(titleRule).not.toMatch(/text-overflow:\s*ellipsis/); + expect(titleRule).not.toMatch(/overflow:\s*hidden/); + expect(titleRule).toMatch(/overflow-wrap:\s*anywhere/); + // The host drops below a wrapping title instead of squeezing it. + expect(rule('.collab-link-chip__text')).toMatch(/flex-wrap:\s*wrap/); + // A grown chip keeps rounded corners rather than a pill's stadium ends. + expect(rule('.collab-link-chip')).not.toMatch(/border-radius:\s*999px/); + }); + it('FE-COMP-LINKS-008: a viewer without edit rights gets no add button', async () => { // collab_edit reserved for the owner, on somebody else's trip. seedStore(usePermissionsStore, { permissions: { collab_edit: 'trip_owner' } }); diff --git a/client/src/index.css b/client/src/index.css index 61f8f6385..0ad77c726 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -1401,7 +1401,9 @@ img[alt="TREK"] { gap: 6px; min-width: 0; padding: 5px 8px 5px 5px; - border-radius: 999px; + /* Half the one-line height, so a one-line chip is still a pill and one whose + title wraps (#2550) keeps its rounded corners instead of a stadium end. */ + border-radius: 18px; background: var(--bg-secondary); border: 1px solid var(--border-faint); box-shadow: var(--shadow-sm); @@ -1453,12 +1455,13 @@ img[alt="TREK"] { .collab-link-chip__main:hover .collab-link-chip__go, .collab-link-chip__main:focus-visible .collab-link-chip__go { opacity: 1; } .collab-link-chip__main:hover .collab-link-chip__icon > :not(.collab-link-chip__go), .collab-link-chip__main:focus-visible .collab-link-chip__icon > :not(.collab-link-chip__go) { opacity: 0; } -/* Title first, host with what is left: a max-content track takes its full width - before the fr track sees any, and shrinks only once the title alone no longer - fits. Two flex items shrank together and both ended in an ellipsis. */ +/* The title is never cut (#2550): it wraps and the chip grows in height, so the + full name is on screen on every device, not behind a tooltip a phone has no + way to show. The host sits beside a title short enough to leave room and drops + to its own line below one that wraps; only the host is allowed an ellipsis. */ .collab-link-chip__text { - display: grid; - grid-template-columns: minmax(0, max-content) minmax(0, 1fr); + display: flex; + flex-wrap: wrap; align-items: baseline; column-gap: 6px; min-width: 0; @@ -1467,11 +1470,11 @@ img[alt="TREK"] { .collab-link-chip__title { font-size: calc(12.5px * var(--fs-scale-body, 1)); font-weight: 600; - line-height: 1.2; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + line-height: 1.25; + white-space: normal; + overflow-wrap: anywhere; min-width: 0; + max-width: 100%; } .collab-link-chip__host { font-size: calc(10.5px * var(--fs-scale-caption, 1)); @@ -1480,6 +1483,7 @@ img[alt="TREK"] { overflow: hidden; text-overflow: ellipsis; min-width: 0; + max-width: 100%; } .collab-link-chip__actions { display: flex;