diff --git a/frontend/src/components/FilterPanel.tsx b/frontend/src/components/FilterPanel.tsx
index 111f992..b7d922f 100644
--- a/frontend/src/components/FilterPanel.tsx
+++ b/frontend/src/components/FilterPanel.tsx
@@ -375,20 +375,25 @@ export function FilterPanel({ onSubmit, currentMetric, isLoading, listingCount,
{ value: FurnishType.FURNISHED, label: 'Furnished' },
{ value: FurnishType.PART_FURNISHED, label: 'Part' },
{ value: FurnishType.UNFURNISHED, label: 'Unfurn.' },
- ].map((option) => (
-
- ))}
+ ].map((option) => {
+ const isSelected = selectedFurnishTypes.includes(option.value);
+ return (
+
+ );
+ })}
)}
diff --git a/frontend/src/components/ListView.tsx b/frontend/src/components/ListView.tsx
index 344fbff..bc2c29c 100644
--- a/frontend/src/components/ListView.tsx
+++ b/frontend/src/components/ListView.tsx
@@ -134,23 +134,31 @@ export function ListView({ listingData, onPropertyClick, highlightedPropertyUrl,
{/* Sort controls */}
Sort:
- {sortOptions.map((option) => (
-
- ))}
+ {sortOptions.map((option) => {
+ const isActive = sortConfig.field === option.field;
+ const ariaSort: 'ascending' | 'descending' | 'none' = isActive
+ ? (sortConfig.order === 'asc' ? 'ascending' : 'descending')
+ : 'none';
+ return (
+
+ );
+ })}
{/* Listing count */}
-
- Showing {sortedFeatures.length.toLocaleString()} properties
+
+ Showing {sortedFeatures.length.toLocaleString('en-GB')} {sortedFeatures.length === 1 ? 'property' : 'properties'}
{/* Property list */}
diff --git a/frontend/src/components/Map.tsx b/frontend/src/components/Map.tsx
index 5afec72..3cb2adb 100644
--- a/frontend/src/components/Map.tsx
+++ b/frontend/src/components/Map.tsx
@@ -42,6 +42,7 @@ export function Map(props: MapProps) {
const updateTimeoutRef = useRef
(null);
const isMapLoadedRef = useRef(false);
const lastDataLengthRef = useRef(0);
+ const hasFittedOnceRef = useRef(false);
const poiMarkersRef = useRef([]);
const isPickingPOIRef = useRef(props.isPickingPOI ?? false);
const onPoiLocationPickRef = useRef(props.onPoiLocationPick);
@@ -103,8 +104,10 @@ export function Map(props: MapProps) {
heatmap.update();
- // Fit bounds only on first load or significant data change
- if (lastDataLengthRef.current === 0 && data.features.length > 0) {
+ // Fit bounds ONLY on the very first load with data; preserve user pan/zoom forever after
+ // (without this, going from N → 0 → M results re-fits because lastDataLengthRef resets to 0)
+ if (!hasFittedOnceRef.current && data.features.length > 0) {
+ hasFittedOnceRef.current = true;
const boundsResult = await heatmap.computeBounds({
clipMin: PERCENTILE_CONFIG.BOUNDS_CLIP_MIN,
clipMax: PERCENTILE_CONFIG.BOUNDS_CLIP_MAX,
@@ -133,6 +136,7 @@ export function Map(props: MapProps) {
mapRef.current.on('load', function () {
isMapLoadedRef.current = true;
lastDataLengthRef.current = 0;
+ hasFittedOnceRef.current = false;
updateHeatmap();
});
mapRef.current.on('click', function (e: mapboxgl.MapMouseEvent) {
diff --git a/frontend/src/components/StatsBar.tsx b/frontend/src/components/StatsBar.tsx
index 1c14211..e74f848 100644
--- a/frontend/src/components/StatsBar.tsx
+++ b/frontend/src/components/StatsBar.tsx
@@ -78,23 +78,23 @@ export function StatsBar({
- {stats.count.toLocaleString()}
- listings
+ {stats.count.toLocaleString('en-GB')}
+ {stats.count === 1 ? 'listing' : 'listings'}
{stats.avgPrice > 0 && (
<>
-
Avg: {formatCurrency(stats.avgPrice)}
+
Avg price {formatCurrency(stats.avgPrice)}
- Avg £/m²: {formatCurrency(stats.avgPricePerSqm)}
+ {formatCurrency(stats.avgPricePerSqm)}/m²
- Avg: {Math.round(stats.avgSize)} m²
+ Size {Math.round(stats.avgSize)} m²
>
)}
diff --git a/frontend/src/components/StreamingProgressBar.tsx b/frontend/src/components/StreamingProgressBar.tsx
index 74da47c..d3ac4ef 100644
--- a/frontend/src/components/StreamingProgressBar.tsx
+++ b/frontend/src/components/StreamingProgressBar.tsx
@@ -33,8 +33,8 @@ export function StreamingProgressBar({ progress, isLoading, onCancel }: Streamin
0
+ ? `${Math.min(Math.max((progress.count / progress.total) * 100, 0), 100)}%`
: '100%',
animation: progress.total ? undefined : 'pulse 1.5s ease-in-out infinite',
}}
diff --git a/frontend/src/components/TaskIndicator.tsx b/frontend/src/components/TaskIndicator.tsx
index b5afead..0f6f960 100644
--- a/frontend/src/components/TaskIndicator.tsx
+++ b/frontend/src/components/TaskIndicator.tsx
@@ -63,7 +63,10 @@ export function TaskIndicator({
const activeTask = activeTaskId ? tasks[activeTaskId] : undefined;
const taskStatus = activeTask ? (activeTask.status as TaskStatus) : null;
const taskResult = activeTask?.phase ? taskStateToResult(activeTask) : null;
- const progressPercentage = (activeTask?.progress ?? 0) * 100;
+ const rawProgress = activeTask?.progress ?? 0;
+ const progressPercentage = Number.isFinite(rawProgress)
+ ? Math.min(Math.max(rawProgress * 100, 0), 100)
+ : 0;
const processed = activeTask?.processed ?? null;
const total = activeTask?.total ?? null;