synodic-client 0.0.1.dev56__tar.gz → 0.0.1.dev61__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 (77) hide show
  1. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/PKG-INFO +2 -2
  2. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/pyproject.toml +3 -3
  3. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/data.py +21 -1
  4. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/init.py +10 -6
  5. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/plugin_row.py +68 -36
  6. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/schema.py +11 -1
  7. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/screen.py +348 -54
  8. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/settings.py +18 -5
  9. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/spinner.py +2 -0
  10. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/tool_update_controller.py +99 -11
  11. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/theme.py +26 -0
  12. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/workers.py +48 -1
  13. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/logging.py +7 -6
  14. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/resolution.py +4 -0
  15. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_gather_packages.py +501 -12
  16. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_logging.py +4 -5
  17. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_settings.py +25 -4
  18. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_update_feedback.py +106 -0
  19. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_init.py +24 -2
  20. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_resolution.py +18 -0
  21. synodic_client-0.0.1.dev61/tests/unit/test_workers.py +163 -0
  22. synodic_client-0.0.1.dev56/tests/unit/test_workers.py +0 -55
  23. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/LICENSE.md +0 -0
  24. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/README.md +0 -0
  25. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/__init__.py +0 -0
  26. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/__main__.py +0 -0
  27. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/__init__.py +0 -0
  28. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/bootstrap.py +0 -0
  29. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/icon.py +0 -0
  30. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/instance.py +0 -0
  31. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/qt.py +0 -0
  32. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/schema.py +0 -0
  33. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/__init__.py +0 -0
  34. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/action_card.py +0 -0
  35. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/card.py +0 -0
  36. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/install.py +0 -0
  37. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/install_workers.py +0 -0
  38. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/log_panel.py +0 -0
  39. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/projects.py +0 -0
  40. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/sidebar.py +0 -0
  41. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/tray.py +0 -0
  42. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/screen/update_banner.py +0 -0
  43. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/update_controller.py +0 -0
  44. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/application/uri.py +0 -0
  45. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/cli.py +0 -0
  46. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/client.py +0 -0
  47. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/config.py +0 -0
  48. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/protocol.py +0 -0
  49. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/py.typed +0 -0
  50. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/schema.py +0 -0
  51. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/startup.py +0 -0
  52. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/synodic_client/updater.py +0 -0
  53. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/__init__.py +0 -0
  54. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/conftest.py +0 -0
  55. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/__init__.py +0 -0
  56. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/__init__.py +0 -0
  57. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/conftest.py +0 -0
  58. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_action_card.py +0 -0
  59. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_install_preview.py +0 -0
  60. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_log_panel.py +0 -0
  61. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_preview_model.py +0 -0
  62. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_sidebar.py +0 -0
  63. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_tray_window_show.py +0 -0
  64. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_update_banner.py +0 -0
  65. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/qt/test_update_controller.py +0 -0
  66. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_cli.py +0 -0
  67. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_client_updater.py +0 -0
  68. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_client_version.py +0 -0
  69. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_config.py +0 -0
  70. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_examples.py +0 -0
  71. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_install.py +0 -0
  72. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_updater.py +0 -0
  73. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/test_uri.py +0 -0
  74. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/windows/__init__.py +0 -0
  75. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/windows/conftest.py +0 -0
  76. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/tests/unit/windows/test_protocol.py +0 -0
  77. {synodic_client-0.0.1.dev56 → synodic_client-0.0.1.dev61}/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.dev56
3
+ Version: 0.0.1.dev61
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,7 +8,7 @@ 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.dev74
11
+ Requires-Dist: porringer>=0.2.1.dev77
12
12
  Requires-Dist: qasync>=0.28.0
13
13
  Requires-Dist: velopack>=0.0.1444.dev49733
14
14
  Requires-Dist: typer>=0.24.1
@@ -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.dev74",
13
+ "porringer>=0.2.1.dev77",
14
14
  "qasync>=0.28.0",
15
15
  "velopack>=0.0.1444.dev49733",
16
16
  "typer>=0.24.1",
17
17
  ]
18
- version = "0.0.1.dev56"
18
+ version = "0.0.1.dev61"
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.4",
38
+ "ruff>=0.15.5",
39
39
  "pyrefly>=0.55.0",
40
40
  ]
41
41
  test = [
@@ -22,6 +22,7 @@ from porringer.schema import (
22
22
  CheckParameters,
23
23
  CheckResult,
24
24
  )
25
+ from porringer.schema.check import RuntimeCheckResult
25
26
 
26
27
  from synodic_client.application.schema import Snapshot
27
28
 
@@ -107,7 +108,26 @@ class DataCoordinator:
107
108
  A list of :class:`CheckResult` per plugin.
108
109
  """
109
110
  params = CheckParameters(plugins=plugins)
110
- return await self._porringer.sync.check_updates(
111
+ return await self._porringer.package.check_updates(
112
+ params,
113
+ plugins=self._snapshot.discovered,
114
+ )
115
+
116
+ async def check_updates_by_runtime(
117
+ self,
118
+ plugins: list[str] | None = None,
119
+ ) -> list[RuntimeCheckResult]:
120
+ """Run per-runtime update detection using cached ``DiscoveredPlugins``.
121
+
122
+ Args:
123
+ plugins: Optional include-set of plugin names. ``None``
124
+ means all plugins.
125
+
126
+ Returns:
127
+ A list of :class:`RuntimeCheckResult` per runtime.
128
+ """
129
+ params = CheckParameters(plugins=plugins)
130
+ return await self._porringer.package.check_updates_by_runtime(
111
131
  params,
112
132
  plugins=self._snapshot.discovered,
113
133
  )
@@ -53,12 +53,16 @@ def run_startup_preamble(exe_path: str | None = None) -> None:
53
53
  # Seed user config from the build config (one-time propagation).
54
54
  seed_user_config_from_build()
55
55
 
56
- register_protocol(exe_path)
56
+ frozen = getattr(sys, 'frozen', False)
57
+
58
+ if frozen:
59
+ register_protocol(exe_path)
57
60
 
58
61
  config = resolve_config()
59
- if config.auto_start:
60
- register_startup(exe_path)
61
- else:
62
- remove_startup()
62
+ if frozen:
63
+ if config.auto_start:
64
+ register_startup(exe_path)
65
+ else:
66
+ remove_startup()
63
67
 
64
- logger.info('Startup preamble complete (auto_start=%s)', config.auto_start)
68
+ logger.info('Startup preamble complete (auto_start=%s, frozen=%s)', config.auto_start, frozen)
@@ -25,6 +25,8 @@ from synodic_client.application.theme import (
25
25
  FILTER_CHIP_STYLE,
26
26
  PLUGIN_KIND_HEADER_STYLE,
27
27
  PLUGIN_PROVIDER_NAME_STYLE,
28
+ PLUGIN_PROVIDER_RUNTIME_TAG_DEFAULT_STYLE,
29
+ PLUGIN_PROVIDER_RUNTIME_TAG_STYLE,
28
30
  PLUGIN_PROVIDER_STATUS_INSTALLED_STYLE,
29
31
  PLUGIN_PROVIDER_STATUS_MISSING_STYLE,
30
32
  PLUGIN_PROVIDER_STYLE,
@@ -140,17 +142,19 @@ class PluginProviderHeader(QFrame):
140
142
  self.setObjectName('pluginProvider')
141
143
  self.setStyleSheet(PLUGIN_PROVIDER_STYLE)
142
144
  self._plugin_name = plugin.name
145
+ self._runtime_tag = ''
146
+ self._signal_key = plugin.name
143
147
  self._update_btn: QPushButton | None = None
144
148
  self._checking_spinner: _RowSpinner | None = None
145
149
 
146
- layout = QHBoxLayout(self)
147
- layout.setContentsMargins(0, 0, 0, 0)
148
- layout.setSpacing(6)
150
+ self._layout = QHBoxLayout(self)
151
+ self._layout.setContentsMargins(0, 0, 0, 0)
152
+ self._layout.setSpacing(6)
149
153
 
150
154
  # Plugin name
151
155
  name_label = QLabel(plugin.name)
152
156
  name_label.setStyleSheet(PLUGIN_PROVIDER_NAME_STYLE)
153
- layout.addWidget(name_label)
157
+ self._layout.addWidget(name_label)
154
158
 
155
159
  # Version
156
160
  version_text = (
@@ -162,7 +166,7 @@ class PluginProviderHeader(QFrame):
162
166
  )
163
167
  version_label = QLabel(version_text)
164
168
  version_label.setStyleSheet(PLUGIN_PROVIDER_VERSION_STYLE)
165
- layout.addWidget(version_label)
169
+ self._layout.addWidget(version_label)
166
170
 
167
171
  # Installed indicator
168
172
  status_label = QLabel('\u25cf' if plugin.installed else '\u25cb')
@@ -170,47 +174,73 @@ class PluginProviderHeader(QFrame):
170
174
  PLUGIN_PROVIDER_STATUS_INSTALLED_STYLE if plugin.installed else PLUGIN_PROVIDER_STATUS_MISSING_STYLE
171
175
  )
172
176
  status_label.setToolTip('Installed' if plugin.installed else 'Not installed')
173
- layout.addWidget(status_label)
177
+ self._layout.addWidget(status_label)
174
178
 
175
- layout.addStretch()
179
+ self._layout.addStretch()
176
180
 
177
181
  # Transient inline error label (hidden by default)
178
182
  self._status_label = QLabel()
179
183
  self._status_label.setStyleSheet(PLUGIN_ROW_ERROR_STYLE)
180
184
  self._status_label.hide()
181
- layout.addWidget(self._status_label)
185
+ self._layout.addWidget(self._status_label)
182
186
 
183
187
  # Auto / Update controls (only for updatable kinds)
184
188
  if show_controls:
185
- toggle_btn = QPushButton('Auto')
186
- toggle_btn.setCheckable(True)
187
- toggle_btn.setChecked(auto_update)
188
- toggle_btn.setStyleSheet(PLUGIN_TOGGLE_STYLE)
189
- toggle_btn.setToolTip('Enable automatic updates for this plugin')
190
- toggle_btn.clicked.connect(
191
- lambda checked: self.auto_update_toggled.emit(self._plugin_name, checked),
192
- )
193
- layout.addWidget(toggle_btn)
189
+ self._build_controls(self._layout, plugin, auto_update, has_updates)
194
190
 
195
- self._checking_spinner = _RowSpinner(self)
196
- layout.addWidget(self._checking_spinner)
191
+ def set_runtime(self, tag: str, label: str = '') -> None:
192
+ """Set runtime identity and optionally insert a runtime tag pill.
197
193
 
198
- update_btn = QPushButton('Update')
199
- update_btn.setStyleSheet(PLUGIN_UPDATE_STYLE)
200
- update_btn.setToolTip(f'Upgrade packages via {plugin.name} now')
201
- update_btn.clicked.connect(
202
- lambda: self.update_requested.emit(self._plugin_name),
194
+ Must be called before the widget is added to a visible layout.
195
+ """
196
+ self._runtime_tag = tag
197
+ self._signal_key = f'{self._plugin_name}:{tag}' if tag else self._plugin_name
198
+ if label:
199
+ is_default = '(default)' in label
200
+ pill = QLabel(label)
201
+ pill.setStyleSheet(
202
+ PLUGIN_PROVIDER_RUNTIME_TAG_DEFAULT_STYLE if is_default else PLUGIN_PROVIDER_RUNTIME_TAG_STYLE
203
203
  )
204
- update_btn.setVisible(has_updates)
205
- self._update_btn = update_btn
206
- layout.addWidget(update_btn)
204
+ # Insert after the name label (index 1)
205
+ self._layout.insertWidget(1, pill)
206
+
207
+ def _build_controls(
208
+ self,
209
+ layout: QHBoxLayout,
210
+ plugin: PluginInfo,
211
+ auto_update: bool,
212
+ has_updates: bool,
213
+ ) -> None:
214
+ """Build Auto/Update control buttons."""
215
+ toggle_btn = QPushButton('Auto')
216
+ toggle_btn.setCheckable(True)
217
+ toggle_btn.setChecked(auto_update)
218
+ toggle_btn.setStyleSheet(PLUGIN_TOGGLE_STYLE)
219
+ toggle_btn.setToolTip('Enable automatic updates for this plugin')
220
+ toggle_btn.clicked.connect(
221
+ lambda checked: self.auto_update_toggled.emit(self._signal_key, checked),
222
+ )
223
+ layout.addWidget(toggle_btn)
224
+
225
+ self._checking_spinner = _RowSpinner(self)
226
+ layout.addWidget(self._checking_spinner)
227
+
228
+ update_btn = QPushButton('Update')
229
+ update_btn.setStyleSheet(PLUGIN_UPDATE_STYLE)
230
+ update_btn.setToolTip(f'Upgrade packages via {plugin.name} now')
231
+ update_btn.clicked.connect(
232
+ lambda: self.update_requested.emit(self._signal_key),
233
+ )
234
+ update_btn.setVisible(has_updates)
235
+ self._update_btn = update_btn
236
+ layout.addWidget(update_btn)
207
237
 
208
- if not plugin.installed:
209
- toggle_btn.setEnabled(False)
210
- toggle_btn.setChecked(False)
211
- toggle_btn.setToolTip('Not installed \u2014 cannot auto-update')
212
- update_btn.setEnabled(False)
213
- update_btn.setToolTip('Not installed \u2014 cannot update')
238
+ if not plugin.installed:
239
+ toggle_btn.setEnabled(False)
240
+ toggle_btn.setChecked(False)
241
+ toggle_btn.setToolTip('Not installed \u2014 cannot auto-update')
242
+ update_btn.setEnabled(False)
243
+ update_btn.setToolTip('Not installed \u2014 cannot update')
214
244
 
215
245
  def set_updating(self, updating: bool) -> None:
216
246
  """Toggle the button between *Updating…* and *Update* states."""
@@ -291,6 +321,8 @@ class PluginRow(QFrame):
291
321
  self.setStyleSheet(PLUGIN_ROW_STYLE)
292
322
  self._plugin_name = data.plugin_name
293
323
  self._package_name = data.name
324
+ self._runtime_tag = data.runtime_tag
325
+ self._signal_key = f'{data.plugin_name}:{data.runtime_tag}' if data.runtime_tag else data.plugin_name
294
326
  self._update_btn: QPushButton | None = None
295
327
  self._remove_btn: QPushButton | None = None
296
328
  self._checking_spinner: _RowSpinner | None = None
@@ -391,7 +423,7 @@ class PluginRow(QFrame):
391
423
  toggle_btn.setToolTip('Auto-update this package')
392
424
  toggle_btn.clicked.connect(
393
425
  lambda checked: self.auto_update_toggled.emit(
394
- self._plugin_name,
426
+ self._signal_key,
395
427
  self._package_name,
396
428
  checked,
397
429
  ),
@@ -408,7 +440,7 @@ class PluginRow(QFrame):
408
440
  update_btn.setFixedWidth(PLUGIN_ROW_UPDATE_WIDTH)
409
441
  update_btn.setToolTip(f'Update {data.name}')
410
442
  update_btn.clicked.connect(
411
- lambda: self.update_requested.emit(self._plugin_name, self._package_name),
443
+ lambda: self.update_requested.emit(self._signal_key, self._package_name),
412
444
  )
413
445
  update_btn.setVisible(data.has_update)
414
446
  self._update_btn = update_btn
@@ -435,7 +467,7 @@ class PluginRow(QFrame):
435
467
  if data.is_global:
436
468
  remove_btn.setToolTip(f'Remove {data.name}')
437
469
  remove_btn.clicked.connect(
438
- lambda: self.remove_requested.emit(self._plugin_name, self._package_name),
470
+ lambda: self.remove_requested.emit(self._signal_key, self._package_name),
439
471
  )
440
472
  else:
441
473
  remove_btn.setEnabled(False)
@@ -23,6 +23,7 @@ from porringer.schema import (
23
23
  SubActionProgress,
24
24
  SyncStrategy,
25
25
  )
26
+ from porringer.schema.plugin import RuntimePackageResult
26
27
 
27
28
  from synodic_client.application.screen.action_card import action_key
28
29
  from synodic_client.application.uri import normalize_manifest_key
@@ -139,6 +140,9 @@ class PluginRowData:
139
140
  host_tool: str = ''
140
141
  """Host-tool name for injected packages."""
141
142
 
143
+ runtime_tag: str = ''
144
+ """Runtime tag for per-runtime packages (e.g. ``\"3.12\"``)."""
145
+
142
146
  project_paths: list[str] = field(default_factory=list)
143
147
  """Filesystem paths for project-scoped packages."""
144
148
 
@@ -150,7 +154,7 @@ class PluginRowData:
150
154
 
151
155
 
152
156
  @dataclass(slots=True)
153
- class _RefreshData:
157
+ class RefreshData:
154
158
  """Internal data bundle returned by ``ToolsView._gather_refresh_data``."""
155
159
 
156
160
  plugins: list[PluginInfo]
@@ -162,6 +166,12 @@ class _RefreshData:
162
166
  manifest_packages: dict[str, set[str]]
163
167
  """Mapping of plugin name → manifest-referenced package names."""
164
168
 
169
+ runtime_packages: dict[str, list[RuntimePackageResult]] = field(default_factory=dict)
170
+ """Mapping of plugin name → per-runtime package results (RuntimeConsumer plugins only)."""
171
+
172
+ default_runtime_executable: Path | None = None
173
+ """Executable path of the resolved default runtime, if any."""
174
+
165
175
 
166
176
  # ---------------------------------------------------------------------------
167
177
  # Install preview data models (from install.py)