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]"
+ />
+
+