diff --git a/.dockerignore b/.dockerignore
index 26e4fd1..d340b4f 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,4 +1,3 @@
-data_sources/
.venv
**/node_modules
**/dist
@@ -9,10 +8,3 @@ server-rs/target
__pycache__
analyses/
*.log
-
-# Exclude data files except the ones we need
-data/*
-!data/wide.parquet
-!data/filtered_uk_pois.parquet
-!data/uk.pmtiles
-!data/postcodes
diff --git a/Dockerfile b/Dockerfile
index dcaa83b..144b06c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -20,12 +20,11 @@ WORKDIR /app
COPY --from=server /app/server-rs/target/release/property-map-server ./
COPY --from=frontend /app/frontend/dist ./dist/
-# Copy data files into the image
-COPY data/wide.parquet ./data/
-COPY data/filtered_uk_pois.parquet ./data/
-COPY data/uk.pmtiles ./data/
-COPY data/postcodes ./data/postcodes/
+# COPY property-data/wide.parquet ./data/
+# COPY property-data/filtered_uk_pois.parquet ./data/
+# COPY property-data/uk.pmtiles ./data/
+# COPY property-data/postcodes ./data/postcodes/
EXPOSE 8001
ENTRYPOINT ["./property-map-server"]
-CMD ["--data", "/app/data/wide.parquet", "--pois", "/app/data/filtered_uk_pois.parquet", "--tiles", "/app/data/uk.pmtiles", "--postcodes", "/app/data/postcodes"]
+CMD ["--data", "/app/data/wide.parquet", "--pois", "/app/data/filtered_uk_pois.parquet", "--tiles", "/app/data/uk.pmtiles", "--postcodes", "/app/data/new_postcode_boundaries"]
diff --git a/README.md b/README.md
index 30c38f8..a0397e9 100644
--- a/README.md
+++ b/README.md
@@ -60,17 +60,13 @@ We give you all the data and tools to become an Well-informed Buyer through the
- scraping
- fix frontend
- - price history
- map hexagons
- dragging
- account management
- stripe
-- saved views
- update texts
-- friendlier filtering
- fix plausible
- move data to raid
-- loading animation on right pane
- extract all user-facing texts into a yaml file for easy editing
- register domain
@@ -90,52 +86,33 @@ FAQ:
## outstadning prompts
-Ensure the website is dynamically scalable and looks great on mobiles. The home/data sources/faq pages should be easy, just ensure responsive scaling. For the dashboard, we need a different layout. Split the screen vertically into 60:40. The top half is the map while the bottom is the filter section. Add a menu drawer on the right hand side where the area/properties/poi tabs will live. When the user clicks on a hexagon, the drawer appears covering 90% of the screen. Theres a large X button to dismiss it but clicking outside works too.
+Add licensing to the app. By default, anonymous users can use the map but only in central london. if they try zooming out, the server refuses to provide data and the users will be prompted to buy a lifetime license to continue (or zoom back in). Just before buying a license, they have to register by providing their email address and password, then they need to complete the stripe check out workflow. Implement the full pocketbase/server/frontend integration. For admins, give an option to generate an invite link, opening which prompts you to register and gives you a free license forever. Have a cool animation with party poppers on the successful acquiring of a license. For non-admin users, allow inviting friends for 30% off the price. Also add a support page that shows my email address, and add a FAQ on the same page too. While doing this, protect the server against DOS-ing.
+
+
+-
+- the area stastics are missing for postcodes, they only work for hexagons
+- in the mobile view, move the property density and previewing colour spectrum to the bottom half of the screen.
+- make the no active filters have less padding on phone
+- add blue/green rollout
+- rename OgImageQuery to ScreenshotQuery
+- make the eye and plus icons and their touch targets twice the size
-Look at the frontend. Once you're logged in, you can save your searches and then look at your saved searches. Add a save search button for logged in users in the header. Also add a saved searches page which shows you the time of saving, the filters selected and a screenshot of the map. Use a similar approach we use to generate og images.
-Add licensing to the app. By default, anonymous users can use the map but only in central london. if they try zooming out, the server refuses to provide data and the users will be prompted to buy a lifetime license to continue (or zoom back in). Just before buying a license, they have to register by providing their email address and password, then they need to complete the stripe check out workflow. Implement the full pocketbase/server/frontend integration. For admins, give an option to generate an invite link, opening which prompts you to register and gives you a free license forever. Have a cool animation with party poppers on the successful acquiring of a license. For non-admin users, allow inviting friends for 30% off the price. Also add a support page that shows my email address, and add a FAQ on the same page too. While doing this, protect the server against DOS-ing.
-
-- put price history under property and on the top of the tab add a description for the histograms
-
-- Can we embedd google street view in an iframe? If so, add it underneath the area page
-
-- You can reach out to ollama at http://ollama:11434/v1. Add a server endpoint that takes the selected filters and results of them shown on the frontend and generates a brief description of the area using qwen3:14b. Make sure that loading the llm summary doesn't block showing anything on the screen and instead add a placeholder animation in the Area tab where this functionality belongs.
-
-- Ensure that the Area & Properties tabs have a loading animation when waiting for data to be fetched
-
-- Add a forgot password workflow for the login page relying on pocketbase
-
-- Make sure the frontend asks for 25% more area than what the user sees on their viewport when fetching hexagons and postcodes to ensure a smoother panning experience.
-
-- The area tab is suppoed to show links to zoopla, rightmove, etc. but it doesn't. Investigate the issue
-
-- The colours of the log in popup are not consistent with the rest of the website, fix it.
-
-- Show a register button in the header too and make it green like the other buttons
-
-- Don't support profile pictures or full names to avoid GDPR requirements.
-
-- the min: number; max: number; p1: number; p99: number; counts: number[] histogram schema doesn't make sense. Have on the min max, width and counts in the schema but ensure min corresponds with the center of the bin and max with the center of the last bin
-
-- move the FEATURE_FORMATS definition to the backend's features.rs
-wide = wide.with_columns(
- (
- pl.col("date_of_transfer").dt.year()
- + (pl.col("date_of_transfer").dt.month() - 1) / 12
- )
- .cast(pl.Float32)
- .alias("transaction_year"),
- )
-
-is ugly, just put the actual datetime inside the parquet and call the column date of last transaction
-
-- make the text on the map white when in dark mode
-
-- move download_map_assets.py into the download module where all the other downloads live. Also, try to find a different way of downloading all icons without hardcoding them
+## name ideas
+
+
+perfect postcodes
+
+golden postcodes
+
+calculated move
+
+the spec
+
+geologic
diff --git a/Taskfile.data.yml b/Taskfile.data.yml
index 5118d7f..9e4958d 100644
--- a/Taskfile.data.yml
+++ b/Taskfile.data.yml
@@ -28,7 +28,7 @@ vars:
INSPIRE_OUTPUT: "{{.DATA_DIR}}/inspire"
OA_BOUNDARIES_OUTPUT: "{{.DATA_DIR}}/oa_boundaries.gpkg"
UPRN_LOOKUP_OUTPUT: "{{.DATA_DIR}}/uprn_lookup.parquet"
- POSTCODE_BOUNDARIES_OUTPUT: "{{.DATA_DIR}}/postcode_boundaries"
+ POSTCODE_BOUNDARIES_OUTPUT: "{{.DATA_DIR}}/new_postcode_boundaries"
tasks:
download:tiles:
@@ -247,8 +247,6 @@ tasks:
- download:oa-boundaries
- download:inspire
- download:uprn-lookup
- status:
- - test -d {{.POSTCODE_BOUNDARIES_OUTPUT}}/units
cmds:
- >-
uv run python -m pipeline.transform.postcode_boundaries
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 3534056..86e9a6d 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -13,6 +13,7 @@ import { fetchWithRetry, apiUrl } from './lib/api';
import { parseUrlState } from './lib/url-state';
import { INITIAL_VIEW_STATE } from './lib/consts';
import { useTheme } from './hooks/useTheme';
+import { useIsMobile } from './hooks/useIsMobile';
import { useAuth } from './hooks/useAuth';
import { useSavedSearches } from './hooks/useSavedSearches';
@@ -87,6 +88,7 @@ export default function App() {
});
const { theme, toggleTheme } = useTheme();
+ const isMobile = useIsMobile();
const {
user,
loading: authLoading,
@@ -207,7 +209,7 @@ export default function App() {
}
return (
-
+
{activePage === 'home' ? (
navigateTo('dashboard')} theme={theme} />
@@ -257,6 +260,7 @@ export default function App() {
onClearPendingInfoFeature={() => setPendingInfoFeature(null)}
onNavigateTo={navigateTo}
onExportStateChange={setExportState}
+ isMobile={isMobile}
/>
)}
{showAuthModal && (
diff --git a/frontend/src/components/faq/FAQPage.tsx b/frontend/src/components/faq/FAQPage.tsx
index 4b21dce..5b57fcf 100644
--- a/frontend/src/components/faq/FAQPage.tsx
+++ b/frontend/src/components/faq/FAQPage.tsx
@@ -65,7 +65,7 @@ const FAQ_ITEMS: FAQItem[] = [
{
question: 'Does this work on mobile?',
answer:
- 'The app is designed for desktop browsers where you have enough screen space for the map, filter panel, and POI/properties panel side by side. It will load on mobile but the experience is best on a larger screen.',
+ 'Yes. On mobile, the dashboard uses a vertical split layout with the map on top and a tabbed panel below for filters, area stats, properties, and POIs. Tapping a hexagon opens a full-screen drawer with the details. The full desktop experience with side-by-side panels is available on screens 768px and wider.',
},
];
diff --git a/frontend/src/components/home/HomePage.tsx b/frontend/src/components/home/HomePage.tsx
index 32101bb..219c26c 100644
--- a/frontend/src/components/home/HomePage.tsx
+++ b/frontend/src/components/home/HomePage.tsx
@@ -40,7 +40,7 @@ export default function HomePage({
{/* Hero */}
-
+
Find where to live, not just what's for sale
-
+
Every neighbourhood
in England & Wales.
@@ -158,7 +158,7 @@ export default function HomePage({
{STATS.map((s) => (
-
{s.value}
+
{s.value}
{s.label}
))}
diff --git a/frontend/src/components/map/AreaPane.tsx b/frontend/src/components/map/AreaPane.tsx
index a7855bb..49aa1db 100644
--- a/frontend/src/components/map/AreaPane.tsx
+++ b/frontend/src/components/map/AreaPane.tsx
@@ -10,7 +10,7 @@ import StackedBarChart from './StackedBarChart';
import StackedEnumChart from './StackedEnumChart';
import PriceHistoryChart from './PriceHistoryChart';
import ExternalSearchLinks from './ExternalSearchLinks';
-import { InfoIcon, CloseIcon } from '../ui/icons';
+import { InfoIcon, CloseIcon, ChevronIcon } from '../ui/icons';
import { CollapsibleGroupHeader } from '../ui/CollapsibleGroupHeader';
import { LightbulbIcon } from '../ui/icons/LightbulbIcon';
import { IconButton } from '../ui/IconButton';
@@ -58,6 +58,7 @@ export default function AreaPane({
const featureGroups = useMemo(() => groupFeaturesByCategory(globalFeatures), [globalFeatures]);
const [infoFeature, setInfoFeature] = useState(null);
const [collapsedGroups, setCollapsedGroups] = useState>(new Set());
+ const [aiSummaryExpanded, setAiSummaryExpanded] = useState(true);
const toggleGroup = (name: string) =>
setCollapsedGroups((prev) => {
@@ -133,41 +134,53 @@ export default function AreaPane({
)}
- {/* AI Summary Card */}
- {(aiSummary || aiSummaryLoading || aiSummaryError) && (
-