synodic-client 0.0.1.dev37__tar.gz → 0.0.1.dev39__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 (65) hide show
  1. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/PKG-INFO +3 -3
  2. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/pyproject.toml +4 -4
  3. synodic_client-0.0.1.dev39/synodic_client/_version.py +1 -0
  4. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/screen/action_card.py +17 -121
  5. synodic_client-0.0.1.dev39/synodic_client/application/screen/install.py +1403 -0
  6. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/screen/log_panel.py +26 -20
  7. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/screen/screen.py +136 -227
  8. synodic_client-0.0.1.dev39/synodic_client/application/screen/sidebar.py +330 -0
  9. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/screen/tray.py +45 -33
  10. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/theme.py +92 -1
  11. synodic_client-0.0.1.dev39/synodic_client/application/workers.py +100 -0
  12. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/client.py +0 -11
  13. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/updater.py +24 -53
  14. synodic_client-0.0.1.dev39/tests/unit/qt/conftest.py +24 -0
  15. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/qt/test_action_card.py +2 -66
  16. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/qt/test_install_preview.py +182 -299
  17. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/qt/test_log_panel.py +54 -41
  18. synodic_client-0.0.1.dev39/tests/unit/qt/test_preview_model.py +195 -0
  19. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/qt/test_settings.py +0 -6
  20. synodic_client-0.0.1.dev39/tests/unit/qt/test_sidebar.py +308 -0
  21. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/qt/test_update_banner.py +0 -6
  22. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/test_client_updater.py +0 -7
  23. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/test_config.py +0 -16
  24. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/test_updater.py +34 -24
  25. synodic_client-0.0.1.dev37/synodic_client/_version.py +0 -1
  26. synodic_client-0.0.1.dev37/synodic_client/application/screen/install.py +0 -1151
  27. synodic_client-0.0.1.dev37/synodic_client/application/workers.py +0 -112
  28. synodic_client-0.0.1.dev37/tests/unit/qt/conftest.py +0 -10
  29. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/LICENSE.md +0 -0
  30. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/README.md +0 -0
  31. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/__init__.py +0 -0
  32. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/__main__.py +0 -0
  33. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/__init__.py +0 -0
  34. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/bootstrap.py +0 -0
  35. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/icon.py +0 -0
  36. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/instance.py +0 -0
  37. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/qt.py +0 -0
  38. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/screen/__init__.py +0 -0
  39. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/screen/card.py +0 -0
  40. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/screen/settings.py +0 -0
  41. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/screen/spinner.py +0 -0
  42. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/screen/update_banner.py +0 -0
  43. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/application/uri.py +0 -0
  44. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/cli.py +0 -0
  45. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/config.py +0 -0
  46. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/logging.py +0 -0
  47. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/protocol.py +0 -0
  48. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/py.typed +0 -0
  49. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/resolution.py +0 -0
  50. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/synodic_client/startup.py +0 -0
  51. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/__init__.py +0 -0
  52. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/conftest.py +0 -0
  53. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/__init__.py +0 -0
  54. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/qt/__init__.py +0 -0
  55. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/qt/test_logging.py +0 -0
  56. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/test_cli.py +0 -0
  57. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/test_client_version.py +0 -0
  58. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/test_examples.py +0 -0
  59. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/test_install.py +0 -0
  60. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/test_resolution.py +0 -0
  61. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/test_uri.py +0 -0
  62. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/windows/__init__.py +0 -0
  63. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/windows/conftest.py +0 -0
  64. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/tests/unit/windows/test_protocol.py +0 -0
  65. {synodic_client-0.0.1.dev37 → synodic_client-0.0.1.dev39}/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.dev37
3
+ Version: 0.0.1.dev39
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
@@ -8,9 +8,9 @@ Project-URL: repository, https://github.com/synodic/synodic-client
8
8
  Requires-Python: <3.15,>=3.14
9
9
  Requires-Dist: pyside6>=6.10.2
10
10
  Requires-Dist: packaging>=26.0
11
- Requires-Dist: porringer>=0.2.1.dev53
11
+ Requires-Dist: porringer>=0.2.1.dev56
12
12
  Requires-Dist: qasync>=0.28.0
13
- Requires-Dist: velopack>=0.0.1442.dev64255
13
+ Requires-Dist: velopack>=0.0.1444.dev49733
14
14
  Requires-Dist: typer>=0.24.1
15
15
  Description-Content-Type: text/markdown
16
16
 
@@ -10,12 +10,12 @@ requires-python = ">=3.14, <3.15"
10
10
  dependencies = [
11
11
  "pyside6>=6.10.2",
12
12
  "packaging>=26.0",
13
- "porringer>=0.2.1.dev53",
13
+ "porringer>=0.2.1.dev56",
14
14
  "qasync>=0.28.0",
15
- "velopack>=0.0.1442.dev64255",
15
+ "velopack>=0.0.1444.dev49733",
16
16
  "typer>=0.24.1",
17
17
  ]
18
- version = "0.0.1.dev37"
18
+ version = "0.0.1.dev39"
19
19
 
20
20
  [project.license]
21
21
  text = "LGPL-3.0-or-later"
@@ -35,7 +35,7 @@ build = [
35
35
  "pyinstaller>=6.19.0",
36
36
  ]
37
37
  lint = [
38
- "ruff>=0.15.2",
38
+ "ruff>=0.15.4",
39
39
  "pyrefly>=0.54.0",
40
40
  ]
41
41
  test = [
@@ -0,0 +1 @@
1
+ __version__ = '0.0.1.dev39'
@@ -1,9 +1,9 @@
1
1
  """Action card widgets for the install preview screen.
2
2
 
3
- Replaces the previous ``QTableWidget`` + ``ExecutionLogPanel`` layout
4
- with compact, self-contained cards one per setup action. Each card
5
- shows essential information (package name, type, version, status) and
6
- expands inline to display execution output during install.
3
+ Each card shows essential information (package name, type, version,
4
+ status badge). During install, execution output is routed to the
5
+ unified :class:`~synodic_client.application.screen.log_panel.ExecutionLogPanel`
6
+ rather than displayed inline.
7
7
 
8
8
  :class:`ActionCard` is the per-action widget.
9
9
  :class:`ActionCardList` is the scrollable container that holds them.
@@ -11,23 +11,19 @@ expands inline to display execution output during install.
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
- import html as html_mod
15
14
  import logging
16
15
 
17
16
  from porringer.backend.command.core.action_builder import PHASE_ORDER
18
17
  from porringer.schema import SetupAction, SetupActionResult, SkipReason
19
18
  from porringer.schema.plugin import PluginKind
20
19
  from PySide6.QtCore import QRect, Qt, QTimer, Signal
21
- from PySide6.QtGui import QColor, QFont, QPainter, QPen, QTextCursor
20
+ from PySide6.QtGui import QColor, QPainter, QPen
22
21
  from PySide6.QtWidgets import (
23
22
  QApplication,
24
23
  QCheckBox,
25
24
  QFrame,
26
25
  QHBoxLayout,
27
26
  QLabel,
28
- QScrollArea,
29
- QSizePolicy,
30
- QTextEdit,
31
27
  QToolButton,
32
28
  QVBoxLayout,
33
29
  QWidget,
@@ -38,7 +34,6 @@ from synodic_client.application.theme import (
38
34
  ACTION_CARD_COMMAND_STYLE,
39
35
  ACTION_CARD_DESC_STYLE,
40
36
  ACTION_CARD_EXECUTING_STYLE,
41
- ACTION_CARD_LOG_STYLE,
42
37
  ACTION_CARD_PACKAGE_STYLE,
43
38
  ACTION_CARD_SKELETON_BAR_STYLE,
44
39
  ACTION_CARD_SKELETON_STYLE,
@@ -61,13 +56,6 @@ from synodic_client.application.theme import (
61
56
  COPY_BTN_STYLE,
62
57
  COPY_FEEDBACK_MS,
63
58
  COPY_ICON,
64
- LOG_COLOR_ERROR,
65
- LOG_COLOR_PHASE,
66
- LOG_COLOR_STDERR,
67
- LOG_COLOR_STDOUT,
68
- LOG_COLOR_SUCCESS,
69
- MONOSPACE_FAMILY,
70
- MONOSPACE_SIZE,
71
59
  )
72
60
 
73
61
  logger = logging.getLogger(__name__)
@@ -214,7 +202,6 @@ class ActionCard(QFrame):
214
202
  self.setObjectName('actionCard')
215
203
  self._action: SetupAction | None = None
216
204
  self._is_skeleton = skeleton
217
- self._log_expanded = False
218
205
  self._checking = False
219
206
  self._check_available_version: str | None = None
220
207
 
@@ -278,7 +265,6 @@ class ActionCard(QFrame):
278
265
  def _init_real_ui(self) -> None:
279
266
  """Build the action card layout."""
280
267
  self.setStyleSheet(ACTION_CARD_STYLE)
281
- self.setCursor(Qt.CursorShape.PointingHandCursor)
282
268
 
283
269
  outer = QVBoxLayout(self)
284
270
  outer.setContentsMargins(6, 6, 6, 6)
@@ -287,7 +273,6 @@ class ActionCard(QFrame):
287
273
  outer.addLayout(self._build_top_row())
288
274
  outer.addWidget(self._build_description_row())
289
275
  outer.addWidget(self._build_command_row())
290
- outer.addWidget(self._build_log_output())
291
276
 
292
277
  def _build_top_row(self) -> QHBoxLayout:
293
278
  """Build the top row: type badge | package name ... version | status/spinner | prerelease."""
@@ -366,40 +351,10 @@ class ActionCard(QFrame):
366
351
  self._command_row.hide()
367
352
  return self._command_row
368
353
 
369
- def _build_log_output(self) -> QTextEdit:
370
- """Build the inline log body (hidden by default)."""
371
- self._log_output = QTextEdit()
372
- self._log_output.setReadOnly(True)
373
- self._log_output.setFont(QFont(MONOSPACE_FAMILY, MONOSPACE_SIZE))
374
- self._log_output.setStyleSheet(ACTION_CARD_LOG_STYLE)
375
- self._log_output.setMinimumHeight(40)
376
- self._log_output.setMaximumHeight(250)
377
- self._log_output.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
378
- self._log_output.hide()
379
- return self._log_output
380
-
381
354
  # ------------------------------------------------------------------
382
- # Mouse events (toggle log)
355
+ # Mouse events (copy button)
383
356
  # ------------------------------------------------------------------
384
357
 
385
- def mousePressEvent(self, event: object) -> None:
386
- """Toggle the inline log body on click."""
387
- if self._is_skeleton or not hasattr(self, '_log_output'):
388
- return
389
- # Don't toggle the log when clicking interactive child widgets
390
- if hasattr(self, '_copy_btn') and self._copy_btn.underMouse():
391
- return
392
- if hasattr(self, '_package_label') and self._package_label.underMouse():
393
- return
394
- if hasattr(self, '_desc_label') and self._desc_label.underMouse():
395
- return
396
- self._toggle_log()
397
-
398
- def _toggle_log(self) -> None:
399
- """Expand or collapse the inline log body."""
400
- self._log_expanded = not self._log_expanded
401
- self._log_output.setVisible(self._log_expanded)
402
-
403
358
  def _copy_command(self) -> None:
404
359
  """Copy the command label text to the clipboard with brief feedback."""
405
360
  clipboard = QApplication.clipboard()
@@ -644,7 +599,8 @@ class ActionCard(QFrame):
644
599
  def set_executing(self) -> None:
645
600
  """Transition the card into the *executing* state.
646
601
 
647
- Shows the inline log body and updates the status badge.
602
+ Updates the status badge. Execution output is routed to the
603
+ unified :class:`~synodic_client.application.screen.log_panel.ExecutionLogPanel`.
648
604
  """
649
605
  if self._is_skeleton:
650
606
  return
@@ -652,37 +608,13 @@ class ActionCard(QFrame):
652
608
  self.setStyleSheet(ACTION_CARD_EXECUTING_STYLE)
653
609
  self._status_label.setText('Running\u2026')
654
610
  self._status_label.setStyleSheet(ACTION_CARD_STATUS_RUNNING)
655
- self._log_expanded = True
656
- self._log_output.setVisible(True)
657
-
658
- def append_output(self, text: str, stream: str | None = None) -> None:
659
- """Append a line of output to the inline log.
660
-
661
- Args:
662
- text: The output line.
663
- stream: ``'stdout'``, ``'stderr'``, or ``None`` for phase messages.
664
- """
665
- if self._is_skeleton or not hasattr(self, '_log_output'):
666
- return
667
-
668
- colour = LOG_COLOR_STDOUT
669
- if stream == 'stderr':
670
- colour = LOG_COLOR_STDERR
671
- elif stream is None:
672
- colour = LOG_COLOR_PHASE
673
-
674
- escaped = html_mod.escape(text)
675
- self._log_output.append(f'<span style="color: {colour};">{escaped}</span>')
676
-
677
- cursor = self._log_output.textCursor()
678
- cursor.movePosition(QTextCursor.MoveOperation.End)
679
- self._log_output.setTextCursor(cursor)
680
611
 
681
612
  def set_result(self, result: SetupActionResult) -> None:
682
613
  """Update the card with the final execution result.
683
614
 
684
- The card returns to the default border style. The log body stays
685
- visible but can be collapsed by clicking the card.
615
+ The card returns to the default border style. Detailed output
616
+ is displayed in the unified
617
+ :class:`~synodic_client.application.screen.log_panel.ExecutionLogPanel`.
686
618
 
687
619
  Args:
688
620
  result: The action execution result.
@@ -696,14 +628,9 @@ class ActionCard(QFrame):
696
628
  label = skip_reason_label(result.skip_reason)
697
629
  self._status_label.setText(label)
698
630
  self._status_label.setStyleSheet(ACTION_CARD_STATUS_SKIPPED)
699
- self.append_output(f'\u23ed Skipped: {label}', None)
700
631
  elif result.success:
701
632
  self._status_label.setText('Done')
702
633
  self._status_label.setStyleSheet(ACTION_CARD_STATUS_DONE)
703
- msg = result.message or 'Completed successfully'
704
- self._log_output.append(
705
- f'<span style="color: {LOG_COLOR_SUCCESS};">\u2713 {html_mod.escape(msg)}</span>',
706
- )
707
634
  # Update version if an upgrade completed
708
635
  new_version = result.available_version or self._check_available_version
709
636
  if new_version:
@@ -712,10 +639,6 @@ class ActionCard(QFrame):
712
639
  else:
713
640
  self._status_label.setText('Failed')
714
641
  self._status_label.setStyleSheet(ACTION_CARD_STATUS_FAILED)
715
- msg = result.message or 'Unknown error'
716
- self._log_output.append(
717
- f'<span style="color: {LOG_COLOR_ERROR};">\u2717 {html_mod.escape(msg)}</span>',
718
- )
719
642
 
720
643
  # ------------------------------------------------------------------
721
644
  # Public API — status text accessors (for counting)
@@ -735,15 +658,12 @@ class ActionCard(QFrame):
735
658
 
736
659
 
737
660
  # ---------------------------------------------------------------------------
738
- # ActionCardList — scrollable container
661
+ # ActionCardList — card container
739
662
  # ---------------------------------------------------------------------------
740
663
 
741
664
 
742
- class ActionCardList(QScrollArea):
743
- """Scrollable container of :class:`ActionCard` widgets.
744
-
745
- Replaces both the ``QTableWidget`` and the ``ExecutionLogPanel`` from
746
- the previous install screen layout. One scrollbar, no nesting.
665
+ class ActionCardList(QWidget):
666
+ """Container of :class:`ActionCard` widgets.
747
667
 
748
668
  Cards are keyed by :func:`action_key` (content-based) so that
749
669
  look-ups work across different ``execute_stream`` runs where the
@@ -756,18 +676,12 @@ class ActionCardList(QScrollArea):
756
676
  def __init__(self, parent: QWidget | None = None) -> None:
757
677
  """Initialise the card list."""
758
678
  super().__init__(parent)
759
- self.setWidgetResizable(True)
760
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
761
- self.setFrameShape(QFrame.Shape.NoFrame)
762
679
 
763
- self._container = QWidget()
764
- self._layout = QVBoxLayout(self._container)
680
+ self._layout = QVBoxLayout(self)
765
681
  self._layout.setContentsMargins(0, 0, 0, 0)
766
682
  self._layout.setSpacing(ACTION_CARD_SPACING)
767
683
  self._layout.addStretch()
768
684
 
769
- self.setWidget(self._container)
770
-
771
685
  self._cards: list[ActionCard] = []
772
686
  self._action_map: dict[tuple[object, ...], ActionCard] = {}
773
687
 
@@ -785,7 +699,7 @@ class ActionCardList(QScrollArea):
785
699
  """
786
700
  self.clear()
787
701
  for _ in range(count):
788
- card = ActionCard(self._container, skeleton=True)
702
+ card = ActionCard(self, skeleton=True)
789
703
  self._layout.insertWidget(self._layout.count() - 1, card)
790
704
  self._cards.append(card)
791
705
 
@@ -810,7 +724,7 @@ class ActionCardList(QScrollArea):
810
724
  self.clear()
811
725
  sorted_actions = sorted(actions, key=action_sort_key)
812
726
  for act in sorted_actions:
813
- card = ActionCard(self._container)
727
+ card = ActionCard(self)
814
728
  card.populate(
815
729
  act,
816
730
  plugin_installed=plugin_installed,
@@ -866,21 +780,3 @@ class ActionCardList(QScrollArea):
866
780
  card.deleteLater()
867
781
  self._cards.clear()
868
782
  self._action_map.clear()
869
-
870
- # ------------------------------------------------------------------
871
- # Scroll helpers
872
- # ------------------------------------------------------------------
873
-
874
- def scroll_to_card(self, card: ActionCard) -> None:
875
- """Ensure *card* is visible in the scroll area."""
876
- self.ensureWidgetVisible(card)
877
-
878
- def scroll_to_card_bottom(self, card: ActionCard) -> None:
879
- """Scroll so the bottom of *card* is visible.
880
-
881
- Used during execution to follow the growing inline log output.
882
- Unlike :meth:`scroll_to_card` (which may show the top of a tall
883
- card), this always brings the bottom edge into view.
884
- """
885
- bottom_y = card.geometry().bottom()
886
- self.ensureVisible(0, bottom_y, 0, 50)