# Dev notes — assumptions that turned out wrong Running list so the same mistakes aren't repeated. Add to it whenever reality contradicts an assumption. ## The macOS system Python can't run the GUI; use a Homebrew Python (2026-09-07) The macOS **CommandLineTools** Python 3.9 (`/Library/Developer/CommandLineTools/ .../python3.9`) ships **Tk 8.5.9**. `ttkbootstrap` 1.10.1 requires **Tk 8.6+**. Symptoms: `TclError: couldn't recognize image data` (window icon) and `TclError: unknown option "-style"` on `ttk.Scrollbar`. **Fix for local GUI testing:** `brew install python-tk@3.12` (pulls `tcl-tk`, gives Python 3.12 + **Tk 9.0**), then: ```bash /opt/homebrew/bin/python3.12 -m venv venv venv/bin/pip install -r requirements.txt venv/bin/python app.py ``` `ttkbootstrap` 1.10.1 runs fine on Tk 9.0 in practice (verified: window builds, Treeview sort, DateEntry, LabelFrame, custom styles all work). - The **Linux Mint target** uses its own `python3-tk` (Tk 8.6+) and is unaffected either way. - `screencapture` from a non-GUI shell fails ("could not create image from display") without Screen-Recording permission — automated screenshots of the running app aren't available here; verify by building the widget tree and driving it programmatically instead. ## `pandas` reads everything as strings by design `DataStore._load` uses `dtype={...: str}` for `postal_code`, `lat`, `lon`. Numeric parsing is deliberately deferred to `get_map_data` (`pd.to_numeric(..., errors="coerce")`). Don't "fix" columns to float on load — blank coordinates and leading-zero PLZ both depend on the string representation. ## Map tiles: Carto needs a key, OSM 403s file:// — use Esri (2026-09-07) Two dead ends before landing on Esri: 1. `folium.Map(tiles="CartoDB positron")` → "API KEY REQUIRED" watermark; Carto moved basemaps behind a key. 2. `tiles="OpenStreetMap"` → worked in `curl` from the dev machine but the **user's browser got HTTP 403 "referer is required by tile usage policy"**. OSM's tile CDN graylists refererless requests, and a `karte.html` opened as a local `file://` sends no `Referer`. A browser can't add one. Dead end for this app's "open a local HTML file" model. Now: **Esri "World Light Gray"** base + reference (labels) overlay (`server.arcgisonline.com/ArcGIS/rest/services/Canvas/…`). No key, no `Referer` check, light/muted look close to the old positron. `MAP_TILES` / `MAP_TILES_LABELS` constants; comment lists Esri Street/Topo as swaps. Lesson: a tile source that works from `curl` or an `http://` page can still fail from `file://`. Test the map by **opening the generated `karte.html` directly**, not just by checking the URL in the HTML. ## pandas turns blank CSV cells into NaN even with `dtype=str` (2026-09-12) Assumption that turned out wrong: forcing a column's dtype (e.g. `dtype={"street": str}`) does **not** stop `pd.read_csv` from parsing a truly empty field as `NaN` (a float) instead of `""`. Verified directly: ```python >>> pd.read_csv(io.StringIO("a\n\n"), dtype={"a": str})["a"][0] nan # not '' >>> str(pd.read_csv(io.StringIO("a\n\n"), dtype={"a": str})["a"][0]) 'nan' # !! ``` This is *why* `lat`/`lon` already needed `_is_blank()` / `_coord_str()` helpers — but the new `street` column (added 2026-09-12) hit it immediately and much harder: it's optional, so *every* existing row has it blank, and without a fix every one of them would show the literal text `nan` in Tab 2. Fix: after `pd.read_csv` (all three load paths — normal, empty-file, backup-recovery), `DataStore._load` runs `self.df["street"] = self.df["street"].fillna("")`. Rule for this codebase: **any column that can legitimately be blank needs an explicit `.fillna("")` (or the `_is_blank`/`_coord_str` treatment) right after `pd.read_csv`** — `dtype=str` alone is not enough. `city`/`postal_code` haven't needed this only because they're rarely actually blank in practice, not because they're immune. ## Tk popup menus: bind ``, not `` (2026-09-07) `widget.bind("", …)` + `menu.tk_popup(x, y)` + `finally: menu.grab_release()` is the Windows idiom and **misfires on X11**: `tk_popup` returns immediately, `grab_release()` drops the menu's grab, and the pending `` then activates whatever entry is under the cursor (the first one). Symptom here: right-click a history row → menu flashes → edit dialog opens. Rules for this codebase: - bind the release event: `RIGHT_CLICK` constant (``, or `` on macOS), - build each context menu **once** (store on `self`), don't recreate per click, - just call `menu.tk_popup(...)` — no `grab_release()`. ## Nominatim query order matters `geocode_city` returns the **first** hit, trying PLZ-qualified first, then each region in `GEOCODE_REGIONS`, then a bare `", Germany"`. Reordering changes which coordinates ambiguous town names resolve to.