# Improvement roadmap
Review of the current code, prioritized for the actual deployment: **one
non-technical user, Linux Mint laptop, offline some of the time, no one else to
fix things when they break.** That ordering puts *data safety* and *robustness*
above features.
Priorities: 🔴 do first · 🟡 worth doing · 🟢 nice to have
**Status (2026-09-07):** items 1, 2 and 3 are implemented. Items 6 and 10 are
partly done (logging added; shared autocomplete helpers extracted; map now
`fit_bounds`; region list is now a constant). See
[changelog.md](changelog.md).
---
## 1. Data safety 🔴 — ✅ done
Implemented: atomic writes (`tmp` + `fsync` + `os.replace`), rotating backups in
`data/backups/` (startup + before every change, keep 20), `EmptyDataError` /
corrupt-file fallback to an empty frame or the newest backup, and all writes
(including append) routed through `_save_csv`. A "restore from backup" button in
the UI is still open; for now it is a manual file copy.
Original finding
`data/orte.csv` is the only copy of the data and there is no safety net.
- **No backups.** A bad edit, a botched delete, or a disk hiccup loses history
permanently. Delete is explicitly irreversible and only guarded by a yes/no
dialog.
- **Non-atomic full rewrites.** `update_row` / `delete_rows` do
`df.to_csv(CSV_PATH)` directly. A crash or power loss mid-write can leave a
truncated file.
- **External-edit clobber.** The CSV is read once at startup. If it is opened in
a spreadsheet and changed while the app runs, the next save silently
overwrites those changes.
- **Empty-file crash.** If `orte.csv` exists but is 0 bytes (e.g. an interrupted
write), `pd.read_csv` raises `EmptyDataError` and the app won't start.
**Suggested changes**
- Write every full rewrite to a temp file in the same directory, then
`os.replace()` it into place (atomic on POSIX).
- On startup, and before each destructive write, copy the current file to
`data/backups/orte-YYYYMMDD-HHMMSS.csv`; keep the last ~20 and prune the rest.
- Add a "Backup jetzt erstellen" / "Aus Backup wiederherstellen" pair in Tab 2,
or at least open the backups folder from a menu.
- Handle `EmptyDataError` / missing columns by falling back to an empty frame
and logging a warning.
## 2. Geocoder thread robustness 🔴 — ✅ done
Implemented: every queue item is wrapped in `try/except Exception` (both the
geocode call and the callback), failures are logged and the loop continues.
`geocode_city` now raises `GeocodingUnavailable` when every attempt hit a
service/network error, and after 3 consecutive such failures the app shows a
one-time "Standortdienst nicht erreichbar" dialog. `RateLimiter` was not
adopted; the manual `sleep(1)` stays.
Original finding
`GeocoderWorker.run` has no error boundary around the per-item work. If
`geocode_city` or a callback raises anything other than
`GeocoderTimedOut` / `GeocoderServiceError` (a network stack error, a bug in a
callback, `GeocoderQuotaExceeded`), the **worker thread dies** and every
subsequent entry stays stuck on `⏳ wird gesucht…` with no error shown. The user
has no way to know geocoding stopped working.
**Suggested changes**
- Wrap each item's processing in `try/except Exception`, log the traceback, mark
that row as failed, and keep the loop alive.
- Consider `geopy.extra.rate_limiter.RateLimiter` (with `swallow_exceptions`)
instead of the manual `sleep(1)`.
- Surface a clear one-time status message when geocoding fails repeatedly
("Standortdienst nicht erreichbar – bitte Internetverbindung prüfen").
## 3. Easier, safer input 🔴 — ✅ done
Implemented: the date field is now `ttkbootstrap.DateEntry` (calendar picker) in
both the entry form and the edit dialog, with `parse_date()` validation on save
(a bad date is rejected with a dialog). The edit dialog gained manual
**Breitengrad / Längengrad** fields. Tab 2 shows rows without coordinates in red,
has a "Nur ohne Koordinaten" filter, a hint line with the count, and a
right-click **Koordinaten suchen** action.
Original finding
For the target user the free-text date field is the biggest usability risk.
- **Date field is unvalidated free text** in both the entry form and
`EditDialog`. A typo like `2026-13-05` or `05.02.2026` is stored verbatim,
breaks date sorting, and never appears correctly on the timeline.
- **No recourse when geocoding fails.** A row that comes back
`⚠ nicht gefunden` saves with blank coordinates and then just silently never
shows on the map. There is no way to type coordinates by hand or retry from
Tab 2.
**Suggested changes**
- Replace the date `Entry` with `ttkbootstrap.DateEntry` (calendar picker) in
both places. If keeping free text, validate on add/save and refuse invalid
dates with a clear message.
- In `EditDialog`, add optional `lat` / `lon` fields so a location can be fixed
manually.
- In Tab 2, highlight rows with missing coordinates (e.g. red text) and add a
filter / right-click "Koordinaten suchen" so failed geocodes are visible and
fixable after the fact.
## 4. Put it under version control 🟡 — partly done
`git init` done and `.gitignore` added (covers `.venv/`, `__pycache__/`,
`data/karte.html`, `data/backups/`, `data/app.log`). Still open: the initial
commit (left to Patrick), and the decision on committing `data/orte.csv` vs. an
example file.
- Decide on `data/orte.csv`: for a personal tool it's reasonable to commit it,
or commit a small `data/orte.example.csv` and ignore the real one.
- This `docs/` folder.
Gives you history, a rollback path, and a clean way to push updates to the
laptop (`git pull`).
## 5. Packaging & distribution 🟡
Today install is: `apt install python3-tk`, create a venv, `pip install`. That's
a one-time terminal session, which is acceptable but fragile (loose version
pins mean a future `pip install` could pull an incompatible pandas/folium).
**Suggested changes**
- Pin all four dependencies to exact versions and regenerate deliberately.
- Ship the `run.sh` + `.desktop` launcher from [setup.md](setup.md) in the repo.
- Optional: build a [PyInstaller](https://pyinstaller.org/) one-file bundle on a
matching Linux box. It bundles Python, Tk, and all deps, so install becomes
"copy one file + double-click" with no apt/venv step. Trade-off: you build it,
and rebuild on dependency updates.
## 6. Diagnostics / logging 🟡 — ✅ done
`_setup_logging()` writes a `RotatingFileHandler` to `data/app.log`
(512 KB × 3). Saves, map generation, geocode failures and uncaught exceptions
are logged. `_on_open_map` now logs and narrows its handling. Open: no in-app
"open log folder" shortcut yet.
## 7. Stable row identity 🟡
Rows are keyed by the pandas index, which is renumbered after every delete. An
in-flight geocode callback for one row can land on another row if the user
deletes something in between. `_apply_edit_geocode` guards against a *deleted*
index but not a *reused* one.
**Suggested change:** add an immutable `id` column (uuid or incrementing int)
written to the CSV, and match callbacks on that instead of the positional index.
## 8. Duplicate handling 🟡
`_on_save_all` detects likely duplicates but saves them anyway with only a
transient status-bar line the user will probably miss. Consider a modal
"Diese Einträge existieren schon – trotzdem speichern?" with a per-row choice,
or at least skip exact duplicates by default.
## 9. Offline map 🟢
`karte.html` loads Leaflet/jQuery/Bootstrap from CDNs and tiles from
`tile.openstreetmap.org` (`MAP_TILES`; switched away from CartoDB positron, which
now needs an API key). The map is blank without internet even though all the
data is local. If offline use matters, vendor the Leaflet JS/CSS into `data/`
and post-process the Folium output to point at local files (tiles would still
need caching or an offline tile pack — larger effort).
## 10. Small code-quality items 🟢 — partly done
- ✅ Shared autocomplete helpers (`filter_locations`, `split_city_plz`)
extracted; `EditDialog` and `App` both use them.
- ✅ Region list is now the `GEOCODE_REGIONS` constant; map centre/zoom are
`MAP_DEFAULT_CENTER` / `MAP_DEFAULT_ZOOM`.
- ✅ `generate_map` now `fit_bounds` to the markers.
- ⬜ Add a `tests/` folder with pytest coverage for `DataStore` (append / update
/ delete / `find_duplicates`) and the input helpers. A non-GUI smoke script
exists (used during development) but is not in the repo — worth formalizing.
- ⬜ Add `ruff` / `mypy` config.
---
## UI/UX review (2026-09-07)
Second review, focused on the interface. Items 1–12 implemented; 13 deferred.
| # | Change | Status |
|---|--------|--------|
| 1 | Larger base font + taller Treeview rows (`UI_FONT_SIZE`) | ✅ |
| 2 | Colour-coded status bar + 8 s auto-clear to a neutral state | ✅ |
| 3 | "Warteschlange leeren" asks for confirmation; more form spacing | ✅ |
| 4 | ▲/▼ arrow on the active sort column in Tab 2 | ✅ |
| 5 | Dropped the now-redundant `(JJJJ-MM-TT)` hint | ✅ |
| 6 | Tab 2: single **"Karte"** column (`✓` / red `fehlt`) instead of raw lat/lon | ✅ |
| 7 | Explicit "Auswahl entfernen" button + Delete key for queue rows | ✅ |
| 8 | Duplicate save shows a modal with the list, not a transient hint | ✅ |
| 9 | Persistent entry count (window title + Tab 2 hint line) | ✅ |
| 10 | "Karte öffnen" with no coordinates shows a dialog | ✅ |
| 11 | Remember window size & position (`data/window.json`) | ✅ |
| 12 | DRK-red header bar + runtime-drawn red-cross icon (`data/icon.png`) | ✅ |
| 13 | One-click "Hinzufügen & sofort speichern" | ⬜ deferred |
Full custom DRK-red *theme* (recolouring `primary` etc.) was **not** done — the
red header bar gives the branding without fighting ttkbootstrap's theme system.
## Suggested order of remaining work
1. Initial git commit (item 4), then decide on committing `orte.csv`.
2. Pinned deps + `run.sh` / `.desktop` launcher in the repo (item 5).
3. Stable `id` column (item 7 of the first review).
4. Formalize tests (item 10); offline map (item 9) only if offline use becomes real.