From a85c70986eede1ab9191730b714c9389c2e45b9f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 9 May 2026 07:26:49 +0000 Subject: [PATCH 1/2] Clear lint errors blocking React Compiler Stabilize selectedSources/compareSources via useMemo so the surrounding useMemo dependencies are reference-stable and the compiler can preserve them. Switch the MapView raster overlay from to next/image fill so the local /public/overlays jpgs go through the optimizer. https://claude.ai/code/session_01PST7Stw5tDeGBhQUjdFN7p --- src/components/MapView.tsx | 8 ++++++-- src/components/Workstation.tsx | 18 ++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/MapView.tsx b/src/components/MapView.tsx index 7c636a2..f261be4 100644 --- a/src/components/MapView.tsx +++ b/src/components/MapView.tsx @@ -1,6 +1,7 @@ "use client"; import { useMemo, useState } from "react"; +import Image from "next/image"; import type { Concession, PublicCompanyProject, SourcePack } from "@/lib/types"; type ProjectWithDistance = PublicCompanyProject & { distanceKm: number }; @@ -272,10 +273,13 @@ function RasterLayer({ filter: string; }) { return ( - source.concessionId === selected.id, - ); - const selectedSources = - selectedSourcesRaw.length > 0 ? selectedSourcesRaw : sourcePacks; - const compareSourcesRaw = sourcePacks.filter( - (source) => source.concessionId === compareSelected.id, - ); - const compareSources = - compareSourcesRaw.length > 0 ? compareSourcesRaw : sourcePacks; + const selectedSources = useMemo(() => { + const raw = sourcePacks.filter((source) => source.concessionId === selected.id); + return raw.length > 0 ? raw : sourcePacks; + }, [selected.id]); + const compareSources = useMemo(() => { + const raw = sourcePacks.filter((source) => source.concessionId === compareSelected.id); + return raw.length > 0 ? raw : sourcePacks; + }, [compareSelected.id]); const closestMapAnchor = nearbyProjects[0]; const selectedCommoditySet = new Set( From a36959c7e3a5b9f87cda7e870609c50ed2e183a1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 9 May 2026 08:17:08 +0000 Subject: [PATCH 2/2] =?UTF-8?q?Accept=20C=C3=B4te=20d'Ivoire=20place=20nam?= =?UTF-8?q?es=20in=20Live=20Intake?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A small offline gazetteer (Abidjan, Yamoussoukro, Bouaké, Yaouré, Kokumbo) lets demos type a place name instead of GeoJSON. The Use button or Enter key resolves the name to lat/lng, fills the JSON textarea, and runs the existing intake pipeline so the same recon workflow fires. https://claude.ai/code/session_01PST7Stw5tDeGBhQUjdFN7p --- src/components/Workstation.tsx | 49 +++++++++++++++++++++++++ src/lib/places.ts | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 src/lib/places.ts diff --git a/src/components/Workstation.tsx b/src/components/Workstation.tsx index 4b50fdd..b39f804 100644 --- a/src/components/Workstation.tsx +++ b/src/components/Workstation.tsx @@ -25,6 +25,7 @@ import projectsData from "@/data/public_companies.json"; import sourcePacksData from "@/data/source_packs.json"; import { getNearbyProjects } from "@/lib/geo"; import { sourcePackToFact } from "@/lib/evidence"; +import { findPlace, listPlaceNames } from "@/lib/places"; import type { Concession, EvidenceItem, @@ -119,6 +120,7 @@ export default function Workstation({ const [focusedProjectId, setFocusedProjectId] = useState(); const [intakeText, setIntakeText] = useState(initialIntakeText || intakeSeedText); const [intakeError, setIntakeError] = useState(""); + const [placeQuery, setPlaceQuery] = useState(""); const [compareId, setCompareId] = useState(staticDemos[1].concession.id); const [headerOpen, setHeaderOpen] = useState(true); const selected = @@ -247,6 +249,30 @@ export default function Workstation({ } } + function applyPlaceName() { + const place = findPlace(placeQuery); + if (!place) { + setIntakeError( + `Could not find "${placeQuery.trim() || "place"}" in Côte d'Ivoire. Try: ${listPlaceNames().join(", ")}.`, + ); + return; + } + const json = JSON.stringify( + { lat: place.lat, lng: place.lng, name: place.name }, + null, + 2, + ); + setIntakeText(json); + setIntakeError(""); + try { + const concession = parseIntakeConcession(json); + setRuntimeConcessions((current) => upsertConcession(current, concession)); + selectConcession(concession.id); + } catch (error) { + setIntakeError(error instanceof Error ? error.message : "Could not parse intake JSON"); + } + } + function activateCoverageLayer(label: string) { switch (label) { case "Map anchors": @@ -477,6 +503,29 @@ export default function Workstation({ Paste a coordinate, GeoJSON Point, Polygon, or MultiPolygon. A point becomes a small license-style package and reuses the public-source recon workflow.

+
+ setPlaceQuery(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + applyPlaceName(); + } + }} + placeholder={`Place name — try: ${listPlaceNames().join(", ")}`} + spellCheck={false} + className="h-9 rounded-md border border-[#2a3140] bg-[#07090d] px-3 text-xs text-[#d5dbea] outline-none transition placeholder:text-[#5a6477] focus:border-[#22c55e]" + /> + +