Skip to content

Merge beta to 1.16.0 - #664

Merged
AgreeDK merged 92 commits into
mainfrom
merge-beta-to-1.16.0
Jul 29, 2026
Merged

Merge beta to 1.16.0#664
AgreeDK merged 92 commits into
mainfrom
merge-beta-to-1.16.0

Conversation

@AgreeDK

@AgreeDK AgreeDK commented Jul 29, 2026

Copy link
Copy Markdown
Member

No description provided.

AgreeDK and others added 30 commits July 14, 2026 05:52
- Document GSAK database import, GGZ export/send-to-GPS, double-click
  to open in browser, Trackables column/tab, custom icon packs,
  Support OpenSAK, and the hint-decode-by-default setting
- Remove stale note claiming GGZ export wasn't available yet
- Fix stale 'Last updated June 2026' footer date
Writing \\n (literal backslash-n) instead of \n (an actual newline
escape) in a language file's source shows up in the UI as a raw
backslash-n instead of a line break. Reported on Facebook for German;
turned out to also affect Danish, French, Dutch, Portuguese, Swedish,
and Czech (11 separate keys there) -- only English was unaffected.

Added a language test (test_no_double_escaped_newlines) to catch this
automatically going forward.
New 'Notify me about beta releases too' checkbox under Settings ->
Updates, off by default. When enabled, a stable-running user is
checked against both the latest stable and latest beta release, and
offered whichever is objectively newer -- same dual-check logic
already used for beta users discovering stable releases.
docs: refresh screenshots (v1.16.0-beta.1)
- Custom waypoint types (Parking Area, Trailhead, Stage, Final Location,
  Reference Point, Waypoint, Hotel/POI, Custom) each get their own
  distinct icon in the table, map and detail panel, instead of all
  sharing the generic "unknown" (?) icon. Icons are overridable via the
  same icons/cache_types/ user-icon mechanism as issue #519.

- #593: removed the 12 unused found-smiley colour variants and the
  per-type colour-selection machinery (_FOUND_COLOR_MAP,
  _get_found_svg/_get_found_svg_for_key, the found= param on
  get_cache_type_icon/get_cache_type_pixmap). Only "gold" (Found overlay
  + "Found" column) and "dark_blue" (DNF overlay) were ever actually
  rendered by the app; the rest was dead code/assets.

- Updated the bundled Custom Icon Guide (new waypoint-icon table,
  found-smiley table trimmed to 2 entries).

- Version bumped via scripts/bump_version.py to 1.16.0-beta.2.
docs: refresh screenshots (v1.16.0-beta.2)
- Center point picker for the distance filter (#511): Home, saved home
  points, selected cache, or a manual coordinate; exposes the existing
  min-distance option alongside max. Built as a standalone widget for
  future reuse (#558).
- "Set as center point" right-click action (#511): recompute Distance
  for all caches from any selected cache/custom waypoint, same
  mechanism as switching Home; shown in the info bar and Home dropdown.
- Fix: hint markup ([br], [Étape], ...) was being ROT13-scrambled
  instead of left as literal markup; [br] now renders as a line break
  in both the cache detail hint tab and KML export (#595).
- Fix: corrected GSAK's freeware date on the landing page, 2019 not
  2021 (#589).
- Bump version to 1.16.0-beta.3 (scripts/bump_version.py) + CHANGELOG.
docs: refresh screenshots (v1.16.0-beta.3)
…#601, #464)

- DatabaseManager.databases now returns entries sorted alphabetically
  (case-insensitive) by name instead of insertion order. This single
  change fixes the dropdown/list ordering everywhere it's consumed:
  toolbar db combo, Manage Databases dialog, Move Caches dialog,
  GSAK import dialog, and the regular import dialog.
- Underlying storage/insertion order in opensak.json is unchanged;
  only the public property's return order changed.
- Added regression test test_databases_are_alphabetically_sorted.
- Updated 3 existing tests in TestCopyDatabase that relied on
  databases[-1] (insertion order) to instead look up the db by name.
- Fixes #601 (duplicate report), closes #531.

- db_created_msg no longer tells the user to click "Switch to this"
  to activate the new database, since new_database() already calls
  switch_to() automatically right after creation. Updated wording in
  all 8 language files. Closes #464.
- src/opensak/__init__.py: __version__ = "1.16.0-beta.4"
- site/user-guide.html: 5 hardcoded version references updated via
  scripts/bump_version.py
- CHANGELOG.md: add 1.16.0-beta.4 entry (database list alphabetical
  sort #531/#601, db_created_msg cleanup #464)
docs: refresh screenshots (v1.16.0-beta.4)
recalculate_distances() now persists the centre point + distance_method
it was run with (per database). On startup, distances_up_to_date()
compares this against the current centre/method and does a cheap
single-row spot-check against the persisted Cache.distance column
before deciding whether a full recalculation is needed.

This makes normal startup skip the O(n) recalc entirely, since
recalculate_distances() already runs on import, home-point change,
and Settings dialog close. The spot-check guards against a database
modified outside this OpenSAK install (e.g. synced from another
machine with a different home point).
docs: refresh screenshots (v1.16.0-beta.5)
…isplay

Same fix as the v1.15.1 hotfix, applied here so the 1.16.0 line also
carries it. See the hotfix-1.15.1 commit for full details.

Fixes: #609
localization: update and fix french translation
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…ta/actions/setup-python-7

ci(deps): bump actions/setup-python from 6 to 7
AgreeDK added 28 commits July 22, 2026 22:16
docs: refresh screenshots (v1.16.0-beta.11)
…inheritance (#617, #618, #606)

#617: _parse_datetime() only handled a bare or 'Z'-suffixed ISO 8601
timestamp. Project-GC's GPX export instead uses an explicit UTC offset
("2026-03-16T00:00:00+00:00"), which silently failed to parse, dropping
hidden_date (and any log dates using the same format). Now tries
datetime.fromisoformat() first and converts properly to UTC, falling back
to the previous strptime patterns.

#618: Logs were deleted and fully rebuilt from the GPX file on every
re-import, so any log not present in that particular file (a PQ only ever
carries a cache's most recent handful of logs) was permanently lost.
Logs are now merged: matching log_id's are updated in place, new ones are
added, and existing logs absent from the current file are left untouched.
log_count, last_log_date and dnf_date are now derived from the merged set
so a partial re-import can no longer move them backwards or wipe them.

#606: Column visibility/widths are saved per database name. A brand-new
database had no such key yet and fell straight back to the hard-coded
defaults, silently reverting any customisation (e.g. added Country/
State/County columns) made in another database. Added a global
"last used" fallback key, updated on every save, that a new database
falls back to before the hard-coded defaults.

Tests added:
- TestParseDatetimeOffsets + 2 import-level tests for #617
- 4 new tests covering log accumulation/update/last_log_date/dnf_date
  consistency across re-imports for #618
- TestLastUsedFallback (5 tests) for #606, using a fake DB manager fixture
- cache_wpt() test helper gained an optional hidden_date parameter

2172 unit tests pass, mypy clean across src/opensak/.
…t Cache

Confirmed with the real GC8T83E.gpx from the reporter (not reproducible
as "Mystery"/"Unknown Cache" with today's beta on this file — that part
of the original report couldn't be reproduced). The actual gap: Community
Celebration Events (a limited-run program, May 2020 - Dec 2021) never
got their own value in Groundspeak's machine-readable <groundspeak:type>
field. geocaching.com's GPX always exports these as plain "Event Cache";
"Community Celebration Event" only appears in the free-text cache name
(e.g. "Karlínská kasárna - Community Celebration Event").

Added a narrow fallback in _parse_wpt(): only when groundspeak:type is
exactly "Event Cache" AND the cache name contains the literal phrase
"Community Celebration Event", reclassify to "Community Celebration
Event" — which already has its own icon/mapping in icon_provider.py and
utils/constants.py, it just was never being reached.

Tests added:
- Real fixture (trimmed from Allan's GC8T83E.gpx) confirms correct
  reclassification
- A plain Event Cache with an unrelated name is NOT reclassified
- A non-Event-type cache that merely mentions the phrase in its name is
  NOT reclassified (fallback only fires on the exact groundspeak:type)

2175 unit tests pass, mypy clean across src/opensak/.
Promotes the [Unreleased] section to a dated v1.16.0-beta.12 heading
covering the GPX-import fix cluster: #617 (hidden date lost from
Project-GC), #618 (logs wiped on re-import instead of accumulating),
#591 (CCE caches shown as generic Event Cache), and #606 (column
settings not inherited by a new database).

Version bumped via scripts/bump_version.py — updates
src/opensak/__init__.py and the 5 hardcoded references in
site/user-guide.html atomically.
docs: refresh screenshots (v1.16.0-beta.12)
The "Choose columns" dialog now supports named, saveable "Column Views"
(visible columns, widths, container/type display), parallel to the
existing saved filter profiles: a dropdown lists saved views, a Save
button (💾) stores the current selection under a name, and a Delete
button removes one. A new "Set as Default" button marks the currently
selected saved view as the global default (shown with a ★ in the
dropdown).

Any database without its own explicit per-database column configuration
— including brand-new databases — now falls back to the designated
default view instead of the hard-coded factory defaults. Clicking OK
still always locks the active database to the chosen configuration,
unchanged from before.

This replaces #606's implicit "last used" fallback
(columns.__last_used__.*), which silently updated on every save
regardless of which database the user actually meant to customise.
Setting the default is now an explicit, deliberate action, and does not
retroactively change databases that already have their own saved
configuration.

A quick-switch Column View dropdown has also been added to the main
toolbar, next to the existing filter-profile dropdown, so a saved view
can be applied to the active database with one click without opening
the dialog. Unlike the filter dropdown, it resets to "(None)" after
each use and on every database switch — it's a one-shot "apply" action,
not a persistent per-database state indicator (there's no dynamic
"follow this view" link, matching the earlier design decision that OK
always locks in a static snapshot).

- New ColumnView class (save/load/list_views), JSON-backed, mirroring
  FilterProfile in filters/engine.py
- get_default_view_name()/set_default_view_name()/get_default_view()
  manage the global default reference
- get_visible_columns(), get_column_widths(), get_container_display(),
  get_type_display() updated to fall back to the default view
- mainwindow.py: new toolbar action + combo (_column_view_combo),
  _populate_column_view_combo()/_on_column_view_combo_changed(); reset
  on database switch (_on_database_switched) and after closing the
  Choose Columns dialog (_open_column_chooser)
- Added 16 new translation keys across all 8 language files (reused
  existing filter_save_btn/filter_saved_title where identical, per
  test_no_globally_redundant_values)
- Replaced TestLastUsedFallback with TestDefaultView in
  tests/unit-tests/test_column_dialog.py (8 tests)

Full unit test suite: 2178 passed. mypy: no issues in 82 source files.
e2e suite could not be run in this sandbox (QtWebEngine aborts at the
qapp fixture here, unrelated to this change) — please run
`xvfb-run pytest -v tests/e2e-tests/` locally as usual before pushing.
…603, #613, #624)

#603 — Country/Region/County columns left-aligned instead of centered
Fabio's #431 centered "similar short-value columns" (Placed By, dates,
container, etc.) but missed Country, Region (the "state" field) and
County — text values of the same kind, just not covered by that sweep.
Added them to the same TextAlignmentRole list for consistency. Updated
test_alignment_role to match (these three move from the "not centered"
group to the "centered" group; Name/GC Code remain left-aligned, as
before).

#624 — Placeholder/hint text invisible in Dark theme
QPalette's PlaceholderText role was never set explicitly in either
_light_palette() or _dark_palette(), so Qt fell back to a derived
default that turned out unreadable in dark mode (reported via
Discussion #620 by @Flookfinders — Where filter, General filter, and
other text boxes). Both palettes now set PlaceholderText explicitly:
a mid-gray (#757575) for light, a lighter gray (#9E9E9E) for dark —
legible against the Base background in both, and dimmer than full Text
so it still reads as a hint rather than a value. Added
TestPlaceholderText with contrast-margin assertions to test_theme.py.

#613 — Where-filter SQL error box unreadable in Windows dark mode
The error QPlainTextEdit in filter_dialog.py's Where tab had a
hardcoded light-theme-only stylesheet (dark-red text on a "transparent"
background). In dark mode that rendered as dark-red-on-dark-gray —
exactly the screenshot in the report. Now picks an explicit,
theme-appropriate style via the existing effective_theme() helper
(already used the same way in settings_dialog.py): a brighter red on an
opaque dark-tinted background for dark mode, unchanged style for light.

Note: fixing this touched get_settings().theme at FilterDialog
construction time, which meant the test suite's mocked SimpleNamespace
settings objects needed a `theme` attribute added — done in both
places in test_filter_dialog.py (the autouse `isolate` fixture and the
TestDistanceUnitPref.dlg_mi fixture) so existing tests keep passing.
Added TestWhereErrorBoxTheme with two new tests asserting the style
actually differs (and no longer relies on "transparent") in dark mode.

Full test suite: 2182 passed (4 new tests). mypy: no issues in 82
source files. e2e suite not run in this sandbox (QtWebEngine aborts at
the qapp fixture here, unrelated to this change) — please run
`xvfb-run pytest -v tests/e2e-tests/` locally as usual before pushing.
Ran scripts/bump_version.py 1.16.0-beta.13 (updates __init__.py and all
5 hardcoded references in site/user-guide.html atomically).

Promoted the [Unreleased] CHANGELOG entries to a dated v1.16.0-beta.13
heading, covering everything merged since beta.12:

- Default Column View (#607) — named, saveable Column Views with a
  toolbar quick-switch dropdown, replacing #606's implicit
  "last used" fallback
- Country/Region/County columns now centered, consistent with #431 (#603)
- Placeholder/hint text now legible in Dark theme (#624)
- Where-filter SQL error box now readable in Windows dark mode (#613)

Full test suite: 2182 passed. mypy: no issues in 82 source files.
bump_version.py --check: consistent.
docs: refresh screenshots (v1.16.0-beta.13)
A beta tester on Facebook reported that columns switched correctly per
database, but the toolbar Column View quick-switch dropdown always
showed "(None)" — even right after applying a saved view, and even for
a database whose configuration matched a saved view exactly.

Root cause: the combo unconditionally reset itself to index 0 ("(None)")
after every apply and after every database switch. That was a
deliberate design choice at the time (a one-shot "apply" action, no
persistent per-database link to a view), but it meant the dropdown never
actually reflected reality.

Fix: _populate_column_view_combo() now compares the active database's
current column configuration (visible columns, widths, container/type
display) against every saved ColumnView, byte-for-byte, via the new
_current_column_view_match() helper, and selects the matching view if
one exists — otherwise "(None)", same as before. This is still a fresh
comparison computed on demand every time, not a stored "this database
follows view X" reference, so it doesn't reopen the dynamic-link design
question that was deliberately closed earlier in #607.

- _on_database_switched: repopulate (with match-detection) instead of
  blindly resetting to index 0
- _on_column_view_combo_changed: after applying a view, repopulate
  instead of forcing index 0 — the combo now naturally shows the
  just-applied view as selected, since the configuration matches it
- New _current_column_view_match() helper on MainWindow
- Added Optional import (typing) for the new helper's return type

Added 3 new e2e tests to tests/e2e-tests/test_e2e_db_combo.py:
- combo shows the matching saved view for the active database
- combo shows "(None)" for a database with a different configuration,
  and correctly re-matches when switching back
- combo reflects the view that was just applied from the toolbar

Full unit test suite: 2182 passed (unchanged — this fix is
MainWindow-level, which has no dedicated unit test file; coverage is
via e2e as usual). mypy: no issues in 82 source files. e2e suite could
not be run in this sandbox (QtWebEngine aborts at the qapp fixture here,
same known limitation as before, unrelated to this change) — please run
`xvfb-run pytest -v tests/e2e-tests/` locally as usual before pushing.
The database grid only ever separated rows vertically via
alternating row colours (horizontal separation), with no visual
separation between columns. This adds a thin vertical line at the
right edge of every column boundary.

setShowGrid() stays False, since it would also add horizontal grid
lines on top of the existing alternating row colours. Instead,
CacheTableView.paintEvent() is overridden: it calls the base
implementation first, then draws a vertical line per column boundary
onto the viewport using the theme's palette Mid colour, so it stays
legible in both light and dark mode without any hardcoded colour.
Lines are limited to the actual row content height, not drawn into
empty space below the last row.

(An earlier draft of this overrode drawRow() instead, which looked
correct on paper but was never actually invoked — PySide6 doesn't
expose drawRow as an overridable virtual. Verified with an
instrumented offscreen render that the call count was 0. Rewritten
to override paintEvent(), which is the standard, reliably-called
hook for this kind of item-view decoration, and confirmed this time
with pixel-level checks on an offscreen render that the lines
actually appear at every column boundary, on every row.)

User-configurable colours, per-user show/hide toggles, and the new
Appearance settings tab proposed in #463 are deliberately out of
scope for now (per Allan's comment on the issue) — basic
functionality comes first. Only the vertical gridlines themselves
are added here.

Closes #463 (partially — colour/visibility settings remain open for
a later date if still wanted).
Ran scripts/bump_version.py 1.16.0-beta.14 (updates __init__.py and all
5 hardcoded references in site/user-guide.html atomically).

Adds a CHANGELOG entry for the vertical gridlines fix (#463) that was
already committed on top of beta.13.
docs: refresh screenshots (v1.16.0-beta.14)
Bundles the #656 investigation's fixes into a beta release so testers
without the source checkout can try it:

- GPX/GGZ export was missing attributes, short/long description, state,
  and full log history (hardcoded to last 5 logs, 500-char text cap).
  All now exported in full, matching GC.com/GSAK.
- Root cause of Garmin firmware not recognizing exported files as
  geocaches at all: switched export from GPX 1.1 (groundspeak:cache
  wrapped in <extensions>) to GPX 1.0 with groundspeak:cache as a direct
  child of <wpt>, matching GSAK's exact convention. Confirmed on a
  GPSMAP 64s: description, logs and hint now all display correctly,
  where previously only the hint displayed.

Version bumped via scripts/bump_version.py (1.16.0-beta.14 ->
1.16.0-beta.15); --check confirms __init__.py and all 5 references in
site/user-guide.html are consistent. CHANGELOG.md updated with a full
entry under the new version heading.

Related: #453, #454, #455, #502 (Send to GPS issue cluster)
docs: refresh screenshots (v1.16.0-beta.15)
…w-up)

Community suggestion on issue #656: GSAK has a toggle that uses the
active database's name as the default export filename instead of a
fixed default, which avoids collisions when switching between multiple
databases without having to manually rename each time.

Added a new checkbox "Use database name as filename" to the GPS export
dialog, checked by default (Allan's decision), applying to both
file-mode export and Send to GPS (device-mode) — same toggle covers
both, per Allan's decision.

Behaviour:
- Checked by default: on dialog open, if a database is currently active
  (get_db_manager().active), the filename field is pre-filled with that
  database's name (sanitized for filesystem safety) instead of the
  previous fixed "opensak" default.
- Falls back to "opensak" if no active database is available.
- Unchecking the box does NOT clear or reset the current filename — it
  only stops the auto-fill going forward, so a manual edit isn't
  clobbered.
- Re-checking it re-fills from the currently active database's name.
- Works alongside the filename-collision handling added in the previous
  commit: if the resulting filename still collides with an existing
  file (e.g. two databases with the same name, or re-exporting the same
  database twice), the existing rename-prompt / delete-checkbox flow
  still applies as before.

New helper: _sanitize_db_name_for_filename() strips/replaces characters
that are invalid in filenames on Windows/Linux/macOS (\ / : * ? " < > |)
and trims surrounding whitespace/dots; falls back to "opensak" only for
a truly empty result.

New translation key gps_use_db_name_cb added to all 8 language files.

Tests:
- Added TestUseDatabaseNameCheckbox (5 tests): checked by default,
  autofill from active database, fallback to "opensak" with no active
  database, unchecking doesn't clear filename, re-checking re-fills.
- Added TestSanitizeDbNameForFilename (5 tests) covering invalid-char
  replacement, normal names, whitespace/dot trimming, an all-invalid
  name (becomes underscores, not a fallback), and empty-string fallback.
- Updated test_device_mode_prompts_on_collision to explicitly disable
  the new checkbox and set a deterministic filename, since it tests
  unrelated collision logic and would otherwise pick up the (now
  default-on) database-name autofill.
- Full suite: 2217 unit tests passed, 258 e2e tests passed.
- mypy: no issues.

Related: #453, #454, #455, #502 (Send to GPS issue cluster)
Custom Waypoints (Hotel/POI, Parking Area, Trailhead, Stage, Final
Location, Reference Point, Waypoint, Custom — issue #141) are stored as
Cache rows for simplicity, but they aren't real geocaches. Exporting
them wrapped in a full groundspeak:cache block made them show up on
Garmin devices as a "fake geocache" with empty D/T stars and
Size: (Not Chosen).

Confirmed fixed on a GPSMAP 64s via GPX import: Hotel/POI and Parking
Area waypoints now come in as plain waypoints with the correct native
icon, instead of looking like empty geocaches.

Adventure Lab stages (cache_type == "Lab Cache") keep their full
groundspeak:cache content as before (description, D/T, hint all
display correctly, confirmed earlier on GPSMAP 64s) — the intent was to
also give them a distinct icon ("Flag, Blue") so they're visually
distinguishable from regular geocaches.

Tested on-device (GPSMAP 64s, both GPX and GGZ): the Lab Cache icon
change had NO visible effect — it still displays with the standard
geocache icon. Working theory: once groundspeak:cache is present, the
device's dedicated geocache display module takes over icon selection
based on groundspeak:type (or just always shows the standard geocache
icon for anything in that module), ignoring the GPX <sym> field
entirely — unlike plain waypoints (no groundspeak:cache), where <sym>
clearly does drive the icon, as shown by the Custom Waypoint fix above
working correctly.

Decision: keep the Lab Cache <sym> change in place anyway (Allan,
2026-07-28) — it's harmless, costs nothing, and may help on other
device models/firmware versions even though it had no effect on the
GPSMAP 64s tested. Getting Lab stages to show a different icon while
keeping full geocache-style content is not achievable without further
investigation (would very likely require dropping groundspeak:cache
entirely, at the cost of losing description/D-T/hint display, which
is not considered a worthwhile trade-off) — not pursuing further for
now.

Changes to generate_gpx() (applies to GGZ too, since it embeds a
generated GPX internally):
- For CUSTOM_WP_TYPES cache_type values: skip the groundspeak:cache
  block entirely, export as a plain GPX waypoint (name/desc/url/sym/
  type only), with <type> set to the plain type name (no misleading
  "Geocache|" prefix).
- New _custom_wp_symbol() maps each CUSTOM_WP_TYPES value to a Garmin
  built-in icon symbol name: Parking Area -> "Parking Area", Trailhead
  -> "Trail Head", Hotel/POI -> "Lodging", Reference Point ->
  "Flag, Green", Stage -> "Flag, Blue", Final Location -> "Flag, Red",
  Waypoint/Custom -> "Waypoint" (also the fallback for anything
  unmapped).
- _cache_symbol() now maps "Lab Cache" -> "Flag, Blue" instead of
  falling through to the generic "Geocache" icon (kept per above, even
  though it had no visible effect on the tested device/firmware).

Not in scope (per earlier discussion): generating proper Garmin custom
POI (.gpi) database files for these categories remains a separate,
bigger effort (OpenSAK still doesn't generate .gpi files at all, unlike
GSAK's Parking Area/Reference Point/Trailhead .gpi exports).

Tests:
- 9 new tests on generate_gpx() covering: custom waypoints have no
  groundspeak:cache block, still carry name/desc, correct symbol per
  type (Parking Area, Hotel/POI, Trailhead), fallback symbol for
  unmapped types, plain (non-prefixed) <type> element, a sanity check
  that regular geocaches are unaffected, and Lab Cache keeping full
  content with just a different <sym> value in the generated XML
  (even though this had no visible on-device effect for Lab Cache).
- 10 new direct unit tests for _custom_wp_symbol() (one per
  CUSTOM_WP_TYPES entry plus fallback/empty-string cases).
- 1 new test on _cache_symbol() confirming Lab Cache gets a distinct
  symbol value from Traditional Cache in the generated XML.
- Full suite: 2237 unit tests passed, 258 e2e tests passed.
- mypy: no issues.
…oint icons

Bundles the recent #656 follow-up work and #660 into a beta release:

- Send to GPS (device-mode) now runs the same filename-collision check
  as file-mode export, following CheminerWill's report that beta.15
  still silently overwrote same-named files on the device. The
  "delete old files before upload" checkbox now also works for GGZ
  (previously GPX-only).
- New "Use database name as filename" checkbox (community suggestion,
  GSAK has an equivalent), checked by default, for both file-mode and
  device-mode exports.
- Custom Waypoints (Hotel/POI, Parking Area, Trailhead, etc.) now
  export as plain GPX waypoints with proper native Garmin icons
  instead of showing up as empty "fake geocaches". Confirmed fixed on
  a GPSMAP 64s via GPX import.
- Adventure Lab stages given a distinct icon attempt (no visible
  effect on the tested device, but harmless and kept in place) — full
  description/D-T/hint content for Lab stages is unaffected either way.

Version bumped via scripts/bump_version.py (1.16.0-beta.15 ->
1.16.0-beta.16); --check confirms __init__.py and all 5 references in
site/user-guide.html are consistent. CHANGELOG.md updated with a full
entry under the new version heading.

Related: #453, #454, #455, #502, #660
docs: refresh screenshots (v1.16.0-beta.16)
CheminerWill's follow-up suggestion after testing beta.16: the "use
database name as filename" checkbox always reset to checked on every
export, but some workflows (frequently exporting named filtered subsets
rather than the whole database) want it to stay unchecked once turned
off, rather than fighting the auto-fill every time.

The checkbox state is now persisted via the existing settings_store
(same JSON-based mechanism used elsewhere, e.g. column_dialog.py) under
the key "gps.use_db_name_as_filename", and restored the next time the
GPS export dialog is opened. Defaults to checked (True) on first-ever
use, when nothing has been saved yet, per Allan's original decision.
Toggling the checkbox in either direction writes the new state
immediately.

The existing autofill-on-check behaviour (fills the filename field with
the active database's name, sanitized) and non-destructive
uncheck-doesn't-clear-filename behaviour are both unchanged.

Tests:
- Added 4 new tests to TestUseDatabaseNameCheckbox: unchecked state
  persists to a newly-opened dialog, checked state persists likewise,
  first-ever run (no stored value) defaults to checked, and toggling
  writes to the settings store immediately in both directions.
- Relies on the existing autouse isolate_settings_store fixture in
  conftest.py, which gives every unit test a fresh in-memory settings
  store — no risk of cross-test leakage or touching real user config.
- Full suite: 2241 unit tests passed, 258 e2e tests passed.
- mypy: no issues.

Related: #656
CheminerWill reported that newly created Custom Waypoints showed no
distance from the center/home point and sorted to the bottom of the
list (as if distance were unset), even though coordinates were entered
correctly. Workaround noted in the issue: switching the center/home
point away and back made the distance appear.

Root cause: Cache.distance/bearing are precomputed, stored columns
(not calculated on the fly at display time) — the cache table model
just reads whatever value is already in the DB. recalculate_distances()
is the function that actually computes and persists them, and it's
called after import and after switching the center/home point, but
_add_waypoint() never called it after inserting a newly created cache,
leaving distance/bearing as their default None. Switching center point
happened to "fix" it only because that action triggers a full
recalculate_distances() pass over every cache in the database,
including the new one.

Fixed in two places (same root cause, same fix):
- _add_waypoint(): recalculate_distances() is now called right after
  the new cache is added, using the same guarded pattern already used
  in _refresh_after_import() (only if a home point is set).
- _edit_waypoint_from_cache(): had the identical issue — editing a
  cache's coordinates left its stored distance/bearing stale (pointing
  at the old location) until the center point was switched. Fixed the
  same way.

recalculate_distances() does a full-table batch SQL update (not a
per-row round trip), so calling it after a single add/edit is still a
single fast query, not a performance concern.

Tests:
- test_add_waypoint_calculates_distance_immediately: confirms a newly
  added cache has non-None distance/bearing right after _add_waypoint(),
  without any extra user action.
- test_edit_waypoint_recalculates_distance_after_coordinate_change:
  edits an existing cache's coordinates to a location far from the
  default home point (Sydney vs. Copenhagen) and confirms the stored
  distance matches a fresh distance_km() calculation, and differs from
  the pre-edit value.
- Full suite: 2241 unit tests passed, 260 e2e tests passed (2 new).
- mypy: no issues.
@AgreeDK
AgreeDK merged commit 911714d into main Jul 29, 2026
7 checks passed
@AgreeDK
AgreeDK deleted the merge-beta-to-1.16.0 branch July 29, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants