Skip to content

FamilySearch tools window: layout, editor context, and link button UX improvements#1

Closed
dsblank wants to merge 171 commits into
pr-2147-fs-loginfrom
functional_and_aes_upgrades_FS
Closed

FamilySearch tools window: layout, editor context, and link button UX improvements#1
dsblank wants to merge 171 commits into
pr-2147-fs-loginfrom
functional_and_aes_upgrades_FS

Conversation

@dsblank

@dsblank dsblank commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Three improvements to the FamilySearch Tools window, built on the existing
pr-2147-fs-login integration.

1. Reorganize FamilySearch tools window layout

Before:

Screenshot from 2026-06-10 15-38-09

After:

image

Moves the FS connection status widget from the top of the window to a
bottom status bar, giving the active person name full-width prominence.
Places the Link FamilySearch ID button directly below the person name,
above the notebook tabs. Replaces the Person actions FlowBox with three
explicit rows: Compare on its own row, Sync from/Sync to on one row, and
Export to FamilySearch on its own row. This gives each action a clear
visual hierarchy and avoids buttons wrapping unpredictably at narrow widths.

2. Clear editor context when Edit Person window is closed

The tools window previously kept the person name and all action buttons
active after the Edit Person dialog was closed, because the window-destroy
signal was never handled. This change connects to destroy on the editor
window and clears _LAST_EDITOR when the tracked editor closes, triggering
an immediate UI refresh so the name resets to "(none)" and all
person-specific buttons are disabled.

3. Change Link button label and sensitivity based on FSFTID state

When an Edit Person window is open:

  • If the person already has an _FSFTID attribute, the button reads
    "Edit FamilySearch ID" and all other action buttons remain enabled.
  • If the person has no _FSFTID attribute, the button reads
    "Add FamilySearch ID" and all other action buttons are disabled
    until an ID is linked.

The FSFTID check reads from the live in-memory editor object (not the
database copy), so a freshly typed ID is detected immediately — even before
the person is saved.

Test plan

  • Open the FamilySearch Tools window with no Edit Person window open → all person-specific buttons disabled, label shows "(none)", Link button shows "Link FamilySearch ID"
  • Open Edit Person for a person without an _FSFTID attribute → Link button shows "Add FamilySearch ID", all other action buttons are disabled
  • Use "Add FamilySearch ID" to link an ID → button switches to "Edit FamilySearch ID" and other buttons become enabled
  • Open Edit Person for a person who already has an _FSFTID → Link button shows "Edit FamilySearch ID", all other buttons enabled
  • Close the Edit Person window → name resets to "(none)", all person-specific buttons disabled

🤖 Generated with Claude Code

Nick-Hall and others added 30 commits April 18, 2026 23:57
MSYS2 is deprecating the MINGW64 build environment, so we are moving to UCRT64
for the Windows AIO build.

This commit includes changes already made in the aio-6.0.7 branch with regards
to pinning certain package versions, as well as switching to UCRT64.

db and bsddb3 packages have been removed for now. Decision pending on whether
to remove this from future builds, or to find a way to build older versions of
db-6.0.30

pygraphviz is failing in the build and the error needs to be resolved.

gspell and enchant have been rolled back in order for spell check to work.
Need to investigate why it doesn't work with the latest version of those packages.

Bump minimum OS required to Windows 10.

Fixes #14149.
Escape appversion and appbuild before passing to sed as replacement text.
Sanitize OutFile, replacing any illegal characters with '_'.
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Gramps/Program
Translate-URL: https://hosted.weblate.org/projects/gramps-project/gramps/
Now that we have switched to ucrt64 this is no longer required.
These issues were identified in comments by our Weblate translators.
…users configuration.

Previously the user config settings were used to set db prefixes which could cause tests to fail. For example an iprefix of "I%05d" would cause the imported gramps_id to be I00044 rather than the default of I0044
…ase closure

On Windows, if the db is not closed, an exception is generated when trying to remove the temporary directory
"PermissionError: [WinError 32] The process cannot access the file because it is being used by another process"
On Windows, warnings such as
<sys>:0: ResourceWarning: unclosed database in <sqlite3.Connection object at 0x000002b2d36131f0>
are generated
bsddb is no longer supported by MSYS2 UCRT64 builds
When no build artifact is generated the upload component defaults to a
warning which still marks the build as passed (green). This change
marks this as a failure so the build should go red and catch someone's
attention.

Also updated the upload component in both AIO builds to v6.
Co-authored-by: stevenyoungs <steve@youngs.cc>
The Addon Manager on the Windows AIO installer invokes the frozen
pip.exe to install each addon's Python dependencies. pip and its
vendored libraries (truststore, rich, urllib3, distlib) import a
number of stdlib submodules conditionally or lazily, and cx_Freeze's
static scan misses them. The result was that installing any addon
with pypi dependencies crashed with an ImportError before pip could
do useful work. The original report surfaced via the S3 Media Uploader
addon complaining that ctypes.wintypes was missing, but the class of
bug applies to any stdlib submodule pulled in on a dynamic code path.

Bundle the affected stdlib packages (ctypes, encodings, email, http,
urllib, importlib) in full via cx_Freeze's packages list so every
submodule is included, and include the single-file module getpass
via the includes list. This removes the failure mode where each new
addon could trip a fresh ModuleNotFoundError.

Also add a smoke test to aio/build.sh that runs the frozen pip.exe
after cx_Freeze finishes and aborts before NSIS packaging if pip
raises ImportError, ModuleNotFoundError, or any traceback. Future
coverage gaps are now caught on the build host rather than by end
users running the Addon Manager.

Fixes #13921.
Currently translated at 95.3% (7202 of 7557 strings)

Translation: Gramps/Program
Translate-URL: https://hosted.weblate.org/projects/gramps-project/gramps/pl/
Currently translated at 96.2% (7270 of 7557 strings)

Translation: Gramps/Program
Translate-URL: https://hosted.weblate.org/projects/gramps-project/gramps/sv/
Currently translated at 95.4% (7215 of 7557 strings)

Translation: Gramps/Program
Translate-URL: https://hosted.weblate.org/projects/gramps-project/gramps/es/
Currently translated at 95.4% (7210 of 7557 strings)

Translation: Gramps/Program
Translate-URL: https://hosted.weblate.org/projects/gramps-project/gramps/sk/
Currently translated at 96.3% (7282 of 7557 strings)

Translation: Gramps/Program
Translate-URL: https://hosted.weblate.org/projects/gramps-project/gramps/nl/
Currently translated at 97.3% (7360 of 7557 strings)

Translation: Gramps/Program
Translate-URL: https://hosted.weblate.org/projects/gramps-project/gramps/pt_PT/
Currently translated at 95.9% (7251 of 7557 strings)

Translation: Gramps/Program
Translate-URL: https://hosted.weblate.org/projects/gramps-project/gramps/fr/
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Gramps/Program
Translate-URL: https://hosted.weblate.org/projects/gramps-project/gramps/
dsblank and others added 28 commits June 9, 2026 00:05
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The original report noted that date columns sort without grouping
dates with qualifiers like about, after, before so they appear in
an arbitrary order.

This happens because get_sort_value() returns only the Julian Day
Number with no modifier or quality information.

This change adds Date.get_sort_key() which creates a sort key
based on the JDN adjusted with scores for modifiers and quality

It also updates date sort columns in the list view treemodels
(peoplemodel, familymodel, eventmodel, mediamodel, citationbasemodel)
to call get_sort_key() instead of get_sort_value().

The key is 'JDN * 12 + modifier_score * 3 + quality_score'

Modifier scores:

  0 - MOD_BEFORE
  1 - MOD_NONE, MOD_FROM, MOD_TO, MOD_SPAN, MOD_RANGE
  2 - MOD_ABOUT
  3 - MOD_AFTER

Quality scores:

  0 - QUAL_NONE
  1 - QUAL_ESTIMATED
  2 - QUAL_CALCULATED

For year-only after-dates the position advances to the first JDN of
the following year, which is correct for all supported calendars.
This ensures 'after 1823' sorts after '1823-12-31'.

The max sort key for 31 Dec 9999 is 64481819 (JDN 5373484 * 12)
which fits in the format string "%09d" used by treemodels.

get_sort_value() is unchanged and continues to be used for date
arithmetic, calendar conversion and span calculations throughout the rest
of the codebase.

Fixes #7761.
Restores support for a user-supplied tile URL in the embedded geography
view. The custom URL is entered in Geography preferences using #Z/#X/#Y
as coordinate placeholders. Example providers include MapTiler NLS
historical Ordnance Survey maps (uk-osgb1888, uk-osgb63k1885, etc.).

OsmGpsMap requires a non-null map-source enum value to honour a custom
repo_uri; using map_source=0 (NULL) causes it to render gray tiles and
ignore the URL entirely. Fix this by passing OPENSTREETMAP_RENDERER,
whose built-in URI is NULL (service defunct), so OsmGpsMap keeps the
supplied repo_uri and fetches tiles from it.

Also fixes the image-format lookup for custom URLs, debounces the
url entry field so it only fires on Enter or focus-out rather than
on every keystroke, and widens the URL input field to span the full
grid width.
…'t seem like its frozen for Persons with lots of sources on FS
…es panel & goes straight to the integrations section if/when the gramps foundation key isn't present
Fix remaining seven issues from dsblank's review:

- Issue 4: Log and surface per-row exceptions in _apply_selected_merge_rows
  rather than silently swallowing them; adjusted success/failure dialogs to
  report how many rows could not be applied.
- Issue 5: Restore _SESSION-first priority in get_session() so a cached
  authenticated session is not replaced by a freshly-constructed but
  not-yet-authenticated one.
- Issue 6: Replace sequential add_persons() with a ThreadPoolExecutor that
  fetches HTTP responses concurrently, then deserializes sequentially to
  avoid shared-state races; replaces removed asyncio parallelism.
- Issue 7: Do not cache None in _FS_PHOTO_CACHE on portrait fetch failure
  so transient errors (HTTP 503, timeout) do not permanently suppress photos
  for the process lifetime.
- Issue 8: Replace all print() calls in cache.py with LOG.warning() via a
  module-level logger, making errors visible from desktop launchers.
- Issue 9: Guard both the familysearchgroup visibility calls and update_menu()
  under a single hasattr(self, "uimanager") check to prevent crashes and
  silent skips before the UI manager is ready.
- Issue 10: Move the blocking HTTP search in _empty_tree_start_person_dialog
  onto a daemon thread with GLib.idle_add callback, eliminating the
  Gtk.main_iteration() flush loop that could fire window-manager close events
  and crash with GObject-already-destroyed errors.

Also address emyoulation's CSS segregation suggestion by splitting all
FamilySearch-specific CSS rules out of gramps.css into a new
familysearch.css file that is loaded alongside gramps.css.  All three
CSS loading sites (viewmanager, compare window, tools window) updated.

Additionally convert all FS source files from CRLF to LF line endings
to match the rest of the Gramps codebase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each test module covers a specific issue from PR gramps-project#2333:

  comparators_test.py – issues 1 & 2: compare_spouses argument order and
    ChildAndParentsRelationship field access fixes.
  importer_test.py  – issue 3: add_child/add_family duplicate-family guard.
  tree_test.py      – issue 6: _fetch_raw and ThreadPoolExecutor behaviour.
  cache_test.py     – issue 8: print() replaced by LOG.warning().
  manager_test.py   – issue 5: _SESSION vs get_active_session() priority.
  compare_gtk_test.py – issues 4 & 7: _apply_selected_merge_rows exception
    tracking and _FS_PHOTO_CACHE no-None fix.
  viewmanager_fs_test.py – issue 9: uimanager guard in _update_familysearch_ui.

GUI tests use sys.modules stubs (gi.repository mocks with GLib integer version
attributes) so they run headless without a display server. viewmanager_fs_test
avoids the GTK-heavy module chain entirely by loading _update_familysearch_ui
via AST extraction from viewmanager.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. comparators.py: replace garbled Python import statement used as a
   display separator ("from deserialize.xml import parse_xml") with
   " @ ".join(filter(None, [desc, place])) in all 6 affected sites.

2. manager.py: clear _SESSION when FamilySearch is disabled so that
   re-enabling constructs a fresh session from current config rather
   than returning a stale cached reference.

3. compare_gtk.py: guard db.commit_person() so it only fires when at
   least one merge row succeeded, preventing a no-op write transaction
   from appearing in the undo history when all rows fail.

4. viewmanager.py: reset _fs_empty_tree_import_prompted in
   post_close_db() so the empty-tree import offer can appear again
   when the user opens a second empty tree in the same session.

5. manager.py / viewmanager.py: extract auth-provider resolution into
   needs_access_code() in manager.py and call it from viewmanager.py,
   eliminating the duplicated ~30-line block that could silently drift.

6. comparators.py: introduce CompareRow(NamedTuple) with 13 named
   fields and update all 22 tuple construction sites to use keyword
   arguments; update _merge_row_from_compare to use named attribute
   access instead of positional data[n] indexing with padding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add _last_modified and _etag typed attributes to the deserializer Person
class so mypy accepts the transport metadata assignments in tree.py.
Add an explicit union type annotation for the outcome variable in
actions.py to allow it to hold either a result list or an Exception.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move LOG initializer after all imports in tree.py.
- Replace six print() calls in deserializer.py with logger.debug().
- Import FS_ATTR_CANON from actions.py in importer.py instead of
  using the hardcoded string "_FSFTID".
- Replace bare except/pass in importer.add_person() with LOG.debug()
  so scan failures are visible at debug level.
- Default familysearch.enable to False so the integration is opt-in
  for users who have never configured FamilySearch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Restore child handles in Family.get_referenced_handles() — they were
  accidentally dropped, breaking callman.py UI tracking for child changes.
- Replace time.mktime(parsedate(…)) with calendar.timegm(parsedate(…)) in
  tree.py and cache.py so that HTTP Last-Modified timestamps are treated as
  UTC rather than local time; also guard against parsedate() returning None
  on a malformed header.
- Fix _datemod → _datmod typo in gui/fs/compare/window.py so the early-return
  optimisation in compare_fs_to_gramps actually fires.
- Stop add_persons() from overwriting already-present _etag/_last_modified
  values in Tree._persons when iterating the global deserialize.Person.index.
- In _find_best_fs_fact_match, prefer a fact that has a date over a dateless
  one as the type-only fallback when the Gramps event has a date.
- Fix the couple loop in compare_spouse_notes / compare_spouses to skip
  couples where neither member is fsid instead of emitting spurious rows.
- Log (not swallow) network and auth exceptions from concurrent FS fetches
  in Tree.add_persons().
- Fix tags.py attribute lookup: replace the ad-hoc alias set with the
  canonical FS_ATTR_CANON/FS_ATTR_OLD/FS_ATTR_HUMAN constants from actions.py,
  and fix get_string() → str() which was silently catching AttributeError
  and skipping every attribute.
- Add regression tests for all nine fixes (75 tests, all passing).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Importing tree at module level in actions.py caused a cycle:
actions -> tree -> fs_import/importer -> actions. Deferred the
import of tree into _bind_global_session, the only function that
uses it, to break the cycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dsblank dsblank force-pushed the functional_and_aes_upgrades_FS branch from 87ca5a4 to e1d2754 Compare June 10, 2026 23:37
@dsblank dsblank closed this Jun 10, 2026
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.