diff --git a/client/src/components/Journey/JourneyEntryCover.test.tsx b/client/src/components/Journey/JourneyEntryCover.test.tsx
index bd33f52fa..994b66580 100644
--- a/client/src/components/Journey/JourneyEntryCover.test.tsx
+++ b/client/src/components/Journey/JourneyEntryCover.test.tsx
@@ -61,8 +61,10 @@ describe('JourneyEntryCover', () => {
/>,
)
- // Once as the badge, once as the fallback title.
- expect(screen.getAllByText('Suggestion').length).toBe(2)
+ // The corner mark says it to a screen reader; the title says it in words,
+ // since a suggestion pulled from a trip place is the one card with no story.
+ expect(screen.getByLabelText('Suggestion')).toBeInTheDocument()
+ expect(screen.getByText('Suggestion')).toBeInTheDocument()
})
it('FE-JRN-COVER-005: mood and weather appear only while the journey keeps those fields', () => {
diff --git a/client/src/components/Journey/JourneyEntryCover.tsx b/client/src/components/Journey/JourneyEntryCover.tsx
index d09accdf8..8a4069b7c 100644
--- a/client/src/components/Journey/JourneyEntryCover.tsx
+++ b/client/src/components/Journey/JourneyEntryCover.tsx
@@ -1,4 +1,4 @@
-import { MapPin } from 'lucide-react'
+import { MapPin, Plus } from 'lucide-react'
import { useTranslation } from '../../i18n'
import { moodMeta, weatherMeta } from '../../mobile/screens/journey/mobileJourneyMeta'
import CountryFlag from '../shared/CountryFlag'
@@ -130,9 +130,17 @@ export default function JourneyEntryCover({
)}
+ {/* A mark rather than the word: "Suggestion" spelled out took a third of a
+ 136px card and pushed the date off it. The plus is the same promise the
+ desktop suggestion card makes with its Add Entry button, and the card's
+ italic, dimmed title carries the rest. */}
{isSuggestion && (
-
- {t('journey.entry.suggestion')}
+
+
)}
diff --git a/client/src/components/Journey/JourneyMap.test.tsx b/client/src/components/Journey/JourneyMap.test.tsx
index 80662d186..08c118e6c 100644
--- a/client/src/components/Journey/JourneyMap.test.tsx
+++ b/client/src/components/Journey/JourneyMap.test.tsx
@@ -48,6 +48,9 @@ vi.mock('leaflet', () => {
divIcon: vi.fn(() => ({})),
latLngBounds: vi.fn(() => ({})),
layerGroup: vi.fn(() => ({ addLayer: vi.fn(), addTo: vi.fn(), remove: vi.fn() })),
+ // The map builds its own attribution control so it can collapse to the credit
+ // alone (see the GL twin: the phone lays a carousel over the bottom edge).
+ control: { attribution: vi.fn(() => ({ addTo: vi.fn() })) },
},
map: vi.fn(() => mockMap),
tileLayer: vi.fn(() => ({ addTo: vi.fn(), setUrl: vi.fn() })),
@@ -55,6 +58,7 @@ vi.mock('leaflet', () => {
polyline: vi.fn(() => { const line: any = { addTo: vi.fn(() => line), bindTooltip: vi.fn(() => line) }; return line }),
divIcon: vi.fn(() => ({})),
latLngBounds: vi.fn(() => ({})),
+ control: { attribution: vi.fn(() => ({ addTo: vi.fn() })) },
};
});
@@ -161,10 +165,11 @@ describe('JourneyMap', () => {
render(
);
- // Each marker calls bindTooltip with the entry label
+ // The tooltip is the same card the GL renderer draws now (#2299), so the title
+ // is inside its markup rather than being the whole label.
const mockMarkerInstance = (L.marker as any).mock.results[0].value;
expect(mockMarkerInstance.bindTooltip).toHaveBeenCalledWith(
- 'Paris',
+ expect.stringContaining('Paris'),
expect.objectContaining({ direction: 'top' }),
);
});
@@ -205,12 +210,12 @@ describe('JourneyMap', () => {
// Tooltips use the entry titles
const mockMarker1 = (L.marker as any).mock.results[0].value;
expect(mockMarker1.bindTooltip).toHaveBeenCalledWith(
- 'Happy Paris',
+ expect.stringContaining('Happy Paris'),
expect.objectContaining({ direction: 'top' }),
);
const mockMarker2 = (L.marker as any).mock.results[1].value;
expect(mockMarker2.bindTooltip).toHaveBeenCalledWith(
- 'Sad Berlin',
+ expect.stringContaining('Sad Berlin'),
expect.objectContaining({ direction: 'top' }),
);
});
@@ -551,7 +556,9 @@ describe('JourneyMap', () => {
]}
/>
);
- const tooltipTitles = vi.mocked(mockedMarker().bindTooltip).mock.calls.map(c => c[0]);
+ const titleOf = (html: unknown) =>
+ String(html).match(/trek-journey-popup-title">([^<]*))?.[1] ?? '';
+ const tooltipTitles = vi.mocked(mockedMarker().bindTooltip).mock.calls.map(c => titleOf(c[0]));
expect(tooltipTitles).toEqual(['Paris', 'Berlin']);
});
@@ -562,7 +569,10 @@ describe('JourneyMap', () => {
entries={[{ id: 'e0', lat: 1, lng: 2, title: null, mood: null, entry_date: '2025-06-01' }]}
/>
);
- expect(mockedMarker().bindTooltip).toHaveBeenCalledWith('Entry', expect.objectContaining({ direction: 'top' }));
+ expect(mockedMarker().bindTooltip).toHaveBeenCalledWith(
+ expect.stringContaining('Entry'),
+ expect.objectContaining({ direction: 'top' }),
+ );
});
it('FE-COMP-JOURNEYMAP-037: unmounting tears the leaflet map down', () => {
@@ -690,7 +700,7 @@ describe('JourneyMap', () => {
const labels = vi.mocked(mockedMarker().bindTooltip).mock.calls.map(c => c[0]);
expect(labels.some(l => typeof l === 'string' && l.includes(' typeof l === 'string' && l.includes('<img src=x onerror="alert(1)">'))).toBe(true);
});
// #1614 — photos placed by their own capture coordinates, collapsed by proximity.
it('FE-COMP-JOURNEYMAP-046: draws one thumbnail per cluster and counts the rest', () => {
diff --git a/client/src/components/Journey/JourneyMapGL.test.tsx b/client/src/components/Journey/JourneyMapGL.test.tsx
index d9e2ea776..c9b99ee58 100644
--- a/client/src/components/Journey/JourneyMapGL.test.tsx
+++ b/client/src/components/Journey/JourneyMapGL.test.tsx
@@ -195,7 +195,8 @@ describe('JourneyMapGL', () => {
render()
expect(mapboxgl.accessToken).toBe('pk.test_token')
expect(mapboxgl.Map).toHaveBeenCalledTimes(1)
- expect(mapOptions()).toMatchObject({ style: 'mapbox://styles/mapbox/standard', attributionControl: true })
+ // Collapsed to its ⓘ rather than a strip of text across the bottom (#2299).
+ expect(mapOptions()).toMatchObject({ style: 'mapbox://styles/mapbox/standard', attributionControl: { compact: true } })
})
it('FE-COMP-JMAPGL-003: maplibre needs no token and opts out of the around-center rotate', () => {
@@ -331,7 +332,7 @@ describe('JourneyMapGL', () => {
const html = popupHtml()
expect(html).toContain('>Louvre<')
expect(html).toContain('trek-journey-popup-place')
- expect(html).toContain('trek-journey-popup-sep')
+ expect(html).toContain('trek-journey-popup-chip')
})
it('FE-COMP-JMAPGL-014: the popup falls back to the location name when the entry has no title', () => {
@@ -349,9 +350,9 @@ describe('JourneyMapGL', () => {
const html = popupHtml()
expect(html).toContain('>Reykjavík<')
- // the location moved to the title line, so there is no place chip left below
+ // the location moved to the title line, so there is no place chip left below.
+ // The date keeps its own chip, which is why only the place one is checked.
expect(html).not.toContain('trek-journey-popup-place')
- expect(html).not.toContain('trek-journey-popup-sep')
})
it('FE-COMP-JMAPGL-015: an entry with neither title nor place still gets a heading', () => {
diff --git a/client/src/mobile/screens/dashboard/MDashboard.tsx b/client/src/mobile/screens/dashboard/MDashboard.tsx
index 3f5067e1d..e52a5b4de 100644
--- a/client/src/mobile/screens/dashboard/MDashboard.tsx
+++ b/client/src/mobile/screens/dashboard/MDashboard.tsx
@@ -412,9 +412,16 @@ function MSpotlightCard({ trip, t, onOpen, actions }: {
)
}
+/**
+ * One fact about the spotlight trip: how long, how many places, how many people.
+ *
+ * Set like the DAY x OF y badge above it, a size down. The three used to be
+ * sentence case while the two badges over them were capitals, so the card had two
+ * voices in the same corner.
+ */
function SpotlightPill({ icon, label }: { icon: React.ReactElement; label: string }): React.ReactElement {
return (
-
+
{icon}
{label}
diff --git a/client/src/mobile/screens/journey/MJourney.tsx b/client/src/mobile/screens/journey/MJourney.tsx
index b98786dac..16622509b 100644
--- a/client/src/mobile/screens/journey/MJourney.tsx
+++ b/client/src/mobile/screens/journey/MJourney.tsx
@@ -103,41 +103,47 @@ function HeroCard({ journey, onOpen }: { journey: JourneyListItem; onOpen: () =>
const src = journeyCoverSrc(journey.cover_image)
return (
+ // Built like the dashboard's spotlight trip: the photo carries the card and
+ // everything written sits on one panel of dark glass at its foot, rather than
+ // a badge floating in one corner and text bleeding into a gradient at the
+ // other. Two hero cards, two different languages, was the complaint.