synodic-client 0.0.1.dev61__tar.gz → 0.0.1.dev62__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/PKG-INFO +1 -1
  2. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/pyproject.toml +1 -1
  3. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/settings.py +9 -0
  4. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/tool_update_controller.py +6 -1
  5. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/update_controller.py +5 -2
  6. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_settings.py +43 -0
  7. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_update_controller.py +54 -0
  8. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/LICENSE.md +0 -0
  9. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/README.md +0 -0
  10. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/__init__.py +0 -0
  11. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/__main__.py +0 -0
  12. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/__init__.py +0 -0
  13. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/bootstrap.py +0 -0
  14. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/data.py +0 -0
  15. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/icon.py +0 -0
  16. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/init.py +0 -0
  17. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/instance.py +0 -0
  18. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/qt.py +0 -0
  19. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/schema.py +0 -0
  20. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/__init__.py +0 -0
  21. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/action_card.py +0 -0
  22. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/card.py +0 -0
  23. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/install.py +0 -0
  24. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/install_workers.py +0 -0
  25. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/log_panel.py +0 -0
  26. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/plugin_row.py +0 -0
  27. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/projects.py +0 -0
  28. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/schema.py +0 -0
  29. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/screen.py +0 -0
  30. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/sidebar.py +0 -0
  31. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/spinner.py +0 -0
  32. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/tray.py +0 -0
  33. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/screen/update_banner.py +0 -0
  34. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/theme.py +0 -0
  35. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/uri.py +0 -0
  36. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/application/workers.py +0 -0
  37. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/cli.py +0 -0
  38. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/client.py +0 -0
  39. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/config.py +0 -0
  40. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/logging.py +0 -0
  41. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/protocol.py +0 -0
  42. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/py.typed +0 -0
  43. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/resolution.py +0 -0
  44. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/schema.py +0 -0
  45. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/startup.py +0 -0
  46. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/synodic_client/updater.py +0 -0
  47. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/__init__.py +0 -0
  48. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/conftest.py +0 -0
  49. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/__init__.py +0 -0
  50. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/__init__.py +0 -0
  51. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/conftest.py +0 -0
  52. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_action_card.py +0 -0
  53. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_gather_packages.py +0 -0
  54. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_install_preview.py +0 -0
  55. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_log_panel.py +0 -0
  56. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_logging.py +0 -0
  57. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_preview_model.py +0 -0
  58. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_sidebar.py +0 -0
  59. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_tray_window_show.py +0 -0
  60. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_update_banner.py +0 -0
  61. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/qt/test_update_feedback.py +0 -0
  62. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_cli.py +0 -0
  63. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_client_updater.py +0 -0
  64. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_client_version.py +0 -0
  65. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_config.py +0 -0
  66. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_examples.py +0 -0
  67. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_init.py +0 -0
  68. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_install.py +0 -0
  69. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_resolution.py +0 -0
  70. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_updater.py +0 -0
  71. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_uri.py +0 -0
  72. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/test_workers.py +0 -0
  73. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/windows/__init__.py +0 -0
  74. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/windows/conftest.py +0 -0
  75. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/windows/test_protocol.py +0 -0
  76. {synodic_client-0.0.1.dev61 → synodic_client-0.0.1.dev62}/tests/unit/windows/test_startup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: synodic_client
3
- Version: 0.0.1.dev61
3
+ Version: 0.0.1.dev62
4
4
  Author-Email: Synodic Software <contact@synodic.software>
5
5
  License: LGPL-3.0-or-later
6
6
  Project-URL: homepage, https://github.com/synodic/synodic-client
@@ -15,7 +15,7 @@ dependencies = [
15
15
  "velopack>=0.0.1444.dev49733",
16
16
  "typer>=0.24.1",
17
17
  ]
18
- version = "0.0.1.dev61"
18
+ version = "0.0.1.dev62"
19
19
 
20
20
  [project.license]
21
21
  text = "LGPL-3.0-or-later"
@@ -305,6 +305,15 @@ class SettingsWindow(QMainWindow):
305
305
  """Re-enable the *Check for Updates* button after a check completes."""
306
306
  self._check_updates_btn.setEnabled(True)
307
307
 
308
+ def update_config(self, config: ResolvedConfig) -> None:
309
+ """Replace the internal config snapshot without emitting signals.
310
+
311
+ Called by controllers that persist timestamps so that the next
312
+ :meth:`sync_from_config` sees fresh data instead of the stale
313
+ snapshot captured at construction time.
314
+ """
315
+ self._config = config
316
+
308
317
  def set_last_checked(self, timestamp: str) -> None:
309
318
  """Update the *last updated* label from an ISO 8601 timestamp."""
310
319
  relative = _format_relative_time(timestamp)
@@ -382,7 +382,12 @@ class ToolUpdateOrchestrator:
382
382
  for pkg_name in result.updated_packages:
383
383
  key = f'{plugin_name}/{pkg_name}' if plugin_name else pkg_name
384
384
  existing[key] = now
385
- update_user_config(last_tool_updates=existing)
385
+ resolved = update_user_config(last_tool_updates=existing)
386
+ # Refresh the config on the tools view so the next rebuild
387
+ # picks up the updated timestamps instead of stale data.
388
+ tools_view_ref = self._window.tools_view
389
+ if tools_view_ref is not None:
390
+ tools_view_ref._config = resolved
386
391
 
387
392
  # Clear updating state on widgets
388
393
  tools_view = self._window.tools_view
@@ -132,7 +132,8 @@ class UpdateController:
132
132
  def _persist_check_timestamp(self) -> None:
133
133
  """Persist the current time as *last_client_update* and refresh the label."""
134
134
  ts = datetime.now(UTC).isoformat()
135
- update_user_config(last_client_update=ts)
135
+ resolved = update_user_config(last_client_update=ts)
136
+ self._settings_window.update_config(resolved)
136
137
  self._settings_window.set_last_checked(ts)
137
138
 
138
139
  def _report_error(self, message: str, *, silent: bool) -> None:
@@ -329,7 +330,9 @@ class UpdateController:
329
330
 
330
331
  # Persist the client-update timestamp (actual update downloaded)
331
332
  ts = datetime.now(UTC).isoformat()
332
- update_user_config(last_client_update=ts)
333
+ resolved = update_user_config(last_client_update=ts)
334
+ self._settings_window.update_config(resolved)
335
+ self._settings_window.set_last_checked(ts)
333
336
 
334
337
  self._pending_version = version
335
338
 
@@ -404,3 +404,46 @@ class TestCheckForUpdatesButton:
404
404
  window.set_checking()
405
405
  assert window._check_updates_btn.isEnabled() is False
406
406
  assert window._update_status_label.text() == 'Checking\u2026'
407
+
408
+
409
+ # ---------------------------------------------------------------------------
410
+ # update_config — silent config refresh
411
+ # ---------------------------------------------------------------------------
412
+
413
+
414
+ class TestUpdateConfig:
415
+ """Verify that update_config refreshes _config without emitting signals."""
416
+
417
+ @staticmethod
418
+ def test_update_config_replaces_internal_config() -> None:
419
+ """update_config should replace _config with the new snapshot."""
420
+ window = _make_window(_make_config(last_client_update=None))
421
+ new_config = _make_config(last_client_update='2026-03-09T12:00:00+00:00')
422
+
423
+ window.update_config(new_config)
424
+
425
+ assert window._config is new_config
426
+ assert window._config.last_client_update == '2026-03-09T12:00:00+00:00'
427
+
428
+ @staticmethod
429
+ def test_update_config_does_not_emit_settings_changed() -> None:
430
+ """update_config must NOT emit settings_changed to avoid circular reinit."""
431
+ window = _make_window()
432
+ signal_spy = MagicMock()
433
+ window.settings_changed.connect(signal_spy)
434
+
435
+ window.update_config(_make_config(update_channel='dev'))
436
+
437
+ signal_spy.assert_not_called()
438
+
439
+ @staticmethod
440
+ def test_sync_after_update_config_uses_new_timestamp() -> None:
441
+ """sync_from_config after update_config should display the refreshed timestamp."""
442
+ window = _make_window(_make_config(last_client_update=None))
443
+ assert window._last_client_update_label.text() == ''
444
+
445
+ new_config = _make_config(last_client_update='2026-03-09T12:00:00+00:00')
446
+ window.update_config(new_config)
447
+ window.sync_from_config()
448
+
449
+ assert 'Last updated:' in window._last_client_update_label.text()
@@ -414,3 +414,57 @@ class TestCheckError:
414
414
  ctrl._on_check_error('timeout', silent=True)
415
415
 
416
416
  assert banner.state.name == 'HIDDEN'
417
+
418
+
419
+ # ---------------------------------------------------------------------------
420
+ # Timestamp sync — _persist_check_timestamp updates config
421
+ # ---------------------------------------------------------------------------
422
+
423
+
424
+ class TestPersistCheckTimestamp:
425
+ """Verify _persist_check_timestamp syncs the settings config."""
426
+
427
+ @staticmethod
428
+ def test_persist_updates_settings_config() -> None:
429
+ """_persist_check_timestamp should call update_config on the settings window."""
430
+ ctrl, _app, _client, _banner, settings = _make_controller()
431
+
432
+ fake_resolved = _make_config(last_client_update='2026-03-09T00:00:00+00:00')
433
+ with patch(
434
+ 'synodic_client.application.update_controller.update_user_config',
435
+ return_value=fake_resolved,
436
+ ):
437
+ ctrl._persist_check_timestamp()
438
+
439
+ settings.update_config.assert_called_once_with(fake_resolved)
440
+ settings.set_last_checked.assert_called_once()
441
+
442
+ @staticmethod
443
+ def test_on_check_finished_success_syncs_config() -> None:
444
+ """A successful check should persist timestamp AND sync settings config."""
445
+ ctrl, _app, _client, _banner, settings = _make_controller()
446
+ result = UpdateInfo(available=False, current_version=Version('1.0.0'))
447
+
448
+ fake_resolved = _make_config(last_client_update='2026-03-09T00:00:00+00:00')
449
+ with patch(
450
+ 'synodic_client.application.update_controller.update_user_config',
451
+ return_value=fake_resolved,
452
+ ):
453
+ ctrl._on_check_finished(result, silent=True)
454
+
455
+ settings.update_config.assert_called_once_with(fake_resolved)
456
+
457
+ @staticmethod
458
+ def test_download_finished_syncs_config_and_label() -> None:
459
+ """_on_download_finished should sync config and update the label."""
460
+ ctrl, _app, _client, _banner, settings = _make_controller(auto_apply=False)
461
+
462
+ fake_resolved = _make_config(last_client_update='2026-03-09T00:00:00+00:00')
463
+ with patch(
464
+ 'synodic_client.application.update_controller.update_user_config',
465
+ return_value=fake_resolved,
466
+ ):
467
+ ctrl._on_download_finished(True, '2.0.0')
468
+
469
+ settings.update_config.assert_called_once_with(fake_resolved)
470
+ settings.set_last_checked.assert_called_once()