py-data-engine 0.1.0__py3-none-any.whl

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 (200) hide show
  1. data_engine/__init__.py +37 -0
  2. data_engine/application/__init__.py +39 -0
  3. data_engine/application/actions.py +42 -0
  4. data_engine/application/catalog.py +151 -0
  5. data_engine/application/control.py +213 -0
  6. data_engine/application/details.py +73 -0
  7. data_engine/application/runtime.py +449 -0
  8. data_engine/application/workspace.py +62 -0
  9. data_engine/authoring/__init__.py +14 -0
  10. data_engine/authoring/builder.py +31 -0
  11. data_engine/authoring/execution/__init__.py +6 -0
  12. data_engine/authoring/execution/app.py +6 -0
  13. data_engine/authoring/execution/context.py +82 -0
  14. data_engine/authoring/execution/continuous.py +176 -0
  15. data_engine/authoring/execution/grouped.py +106 -0
  16. data_engine/authoring/execution/logging.py +83 -0
  17. data_engine/authoring/execution/polling.py +135 -0
  18. data_engine/authoring/execution/runner.py +210 -0
  19. data_engine/authoring/execution/single.py +171 -0
  20. data_engine/authoring/flow.py +361 -0
  21. data_engine/authoring/helpers.py +160 -0
  22. data_engine/authoring/model.py +59 -0
  23. data_engine/authoring/primitives.py +430 -0
  24. data_engine/authoring/services.py +42 -0
  25. data_engine/devtools/__init__.py +3 -0
  26. data_engine/devtools/project_ast_map.py +503 -0
  27. data_engine/docs/__init__.py +1 -0
  28. data_engine/docs/sphinx_source/_static/custom.css +13 -0
  29. data_engine/docs/sphinx_source/api.rst +42 -0
  30. data_engine/docs/sphinx_source/conf.py +37 -0
  31. data_engine/docs/sphinx_source/guides/app-runtime-and-workspaces.md +397 -0
  32. data_engine/docs/sphinx_source/guides/authoring-flow-modules.md +215 -0
  33. data_engine/docs/sphinx_source/guides/configuring-flows.md +185 -0
  34. data_engine/docs/sphinx_source/guides/core-concepts.md +208 -0
  35. data_engine/docs/sphinx_source/guides/database-methods.md +107 -0
  36. data_engine/docs/sphinx_source/guides/duckdb-helpers.md +462 -0
  37. data_engine/docs/sphinx_source/guides/flow-context.md +538 -0
  38. data_engine/docs/sphinx_source/guides/flow-methods.md +206 -0
  39. data_engine/docs/sphinx_source/guides/getting-started.md +271 -0
  40. data_engine/docs/sphinx_source/guides/project-inventory.md +5683 -0
  41. data_engine/docs/sphinx_source/guides/project-map.md +118 -0
  42. data_engine/docs/sphinx_source/guides/recipes.md +268 -0
  43. data_engine/docs/sphinx_source/index.rst +22 -0
  44. data_engine/domain/__init__.py +92 -0
  45. data_engine/domain/actions.py +69 -0
  46. data_engine/domain/catalog.py +128 -0
  47. data_engine/domain/details.py +214 -0
  48. data_engine/domain/diagnostics.py +56 -0
  49. data_engine/domain/errors.py +104 -0
  50. data_engine/domain/inspection.py +99 -0
  51. data_engine/domain/logs.py +118 -0
  52. data_engine/domain/operations.py +172 -0
  53. data_engine/domain/operator.py +72 -0
  54. data_engine/domain/runs.py +155 -0
  55. data_engine/domain/runtime.py +279 -0
  56. data_engine/domain/source_state.py +17 -0
  57. data_engine/domain/support.py +54 -0
  58. data_engine/domain/time.py +23 -0
  59. data_engine/domain/workspace.py +159 -0
  60. data_engine/flow_modules/__init__.py +1 -0
  61. data_engine/flow_modules/flow_module_compiler.py +179 -0
  62. data_engine/flow_modules/flow_module_loader.py +201 -0
  63. data_engine/helpers/__init__.py +25 -0
  64. data_engine/helpers/duckdb.py +705 -0
  65. data_engine/hosts/__init__.py +1 -0
  66. data_engine/hosts/daemon/__init__.py +23 -0
  67. data_engine/hosts/daemon/app.py +221 -0
  68. data_engine/hosts/daemon/bootstrap.py +69 -0
  69. data_engine/hosts/daemon/client.py +465 -0
  70. data_engine/hosts/daemon/commands.py +64 -0
  71. data_engine/hosts/daemon/composition.py +310 -0
  72. data_engine/hosts/daemon/constants.py +15 -0
  73. data_engine/hosts/daemon/entrypoints.py +97 -0
  74. data_engine/hosts/daemon/lifecycle.py +191 -0
  75. data_engine/hosts/daemon/manager.py +272 -0
  76. data_engine/hosts/daemon/ownership.py +126 -0
  77. data_engine/hosts/daemon/runtime_commands.py +188 -0
  78. data_engine/hosts/daemon/runtime_control.py +31 -0
  79. data_engine/hosts/daemon/server.py +84 -0
  80. data_engine/hosts/daemon/shared_state.py +147 -0
  81. data_engine/hosts/daemon/state_sync.py +101 -0
  82. data_engine/platform/__init__.py +1 -0
  83. data_engine/platform/identity.py +35 -0
  84. data_engine/platform/local_settings.py +146 -0
  85. data_engine/platform/theme.py +259 -0
  86. data_engine/platform/workspace_models.py +190 -0
  87. data_engine/platform/workspace_policy.py +333 -0
  88. data_engine/runtime/__init__.py +1 -0
  89. data_engine/runtime/file_watch.py +185 -0
  90. data_engine/runtime/ledger_models.py +116 -0
  91. data_engine/runtime/runtime_db.py +938 -0
  92. data_engine/runtime/shared_state.py +523 -0
  93. data_engine/services/__init__.py +49 -0
  94. data_engine/services/daemon.py +64 -0
  95. data_engine/services/daemon_state.py +40 -0
  96. data_engine/services/flow_catalog.py +102 -0
  97. data_engine/services/flow_execution.py +48 -0
  98. data_engine/services/ledger.py +85 -0
  99. data_engine/services/logs.py +65 -0
  100. data_engine/services/runtime_binding.py +105 -0
  101. data_engine/services/runtime_execution.py +126 -0
  102. data_engine/services/runtime_history.py +62 -0
  103. data_engine/services/settings.py +58 -0
  104. data_engine/services/shared_state.py +28 -0
  105. data_engine/services/theme.py +59 -0
  106. data_engine/services/workspace_provisioning.py +224 -0
  107. data_engine/services/workspaces.py +74 -0
  108. data_engine/ui/__init__.py +3 -0
  109. data_engine/ui/cli/__init__.py +19 -0
  110. data_engine/ui/cli/app.py +161 -0
  111. data_engine/ui/cli/commands_doctor.py +178 -0
  112. data_engine/ui/cli/commands_run.py +80 -0
  113. data_engine/ui/cli/commands_start.py +100 -0
  114. data_engine/ui/cli/commands_workspace.py +97 -0
  115. data_engine/ui/cli/dependencies.py +44 -0
  116. data_engine/ui/cli/parser.py +56 -0
  117. data_engine/ui/gui/__init__.py +25 -0
  118. data_engine/ui/gui/app.py +116 -0
  119. data_engine/ui/gui/bootstrap.py +487 -0
  120. data_engine/ui/gui/bootstrapper.py +140 -0
  121. data_engine/ui/gui/cache_models.py +23 -0
  122. data_engine/ui/gui/control_support.py +185 -0
  123. data_engine/ui/gui/controllers/__init__.py +6 -0
  124. data_engine/ui/gui/controllers/flows.py +439 -0
  125. data_engine/ui/gui/controllers/runtime.py +245 -0
  126. data_engine/ui/gui/dialogs/__init__.py +12 -0
  127. data_engine/ui/gui/dialogs/messages.py +88 -0
  128. data_engine/ui/gui/dialogs/previews.py +222 -0
  129. data_engine/ui/gui/helpers/__init__.py +62 -0
  130. data_engine/ui/gui/helpers/inspection.py +81 -0
  131. data_engine/ui/gui/helpers/lifecycle.py +112 -0
  132. data_engine/ui/gui/helpers/scroll.py +28 -0
  133. data_engine/ui/gui/helpers/theming.py +87 -0
  134. data_engine/ui/gui/icons/dark_light.svg +12 -0
  135. data_engine/ui/gui/icons/documentation.svg +1 -0
  136. data_engine/ui/gui/icons/failed.svg +3 -0
  137. data_engine/ui/gui/icons/group.svg +4 -0
  138. data_engine/ui/gui/icons/home.svg +2 -0
  139. data_engine/ui/gui/icons/manual.svg +2 -0
  140. data_engine/ui/gui/icons/poll.svg +2 -0
  141. data_engine/ui/gui/icons/schedule.svg +4 -0
  142. data_engine/ui/gui/icons/settings.svg +2 -0
  143. data_engine/ui/gui/icons/started.svg +3 -0
  144. data_engine/ui/gui/icons/success.svg +3 -0
  145. data_engine/ui/gui/icons/view-log.svg +3 -0
  146. data_engine/ui/gui/icons.py +50 -0
  147. data_engine/ui/gui/launcher.py +48 -0
  148. data_engine/ui/gui/presenters/__init__.py +72 -0
  149. data_engine/ui/gui/presenters/docs.py +140 -0
  150. data_engine/ui/gui/presenters/logs.py +58 -0
  151. data_engine/ui/gui/presenters/runtime_projection.py +29 -0
  152. data_engine/ui/gui/presenters/sidebar.py +88 -0
  153. data_engine/ui/gui/presenters/steps.py +148 -0
  154. data_engine/ui/gui/presenters/workspace.py +39 -0
  155. data_engine/ui/gui/presenters/workspace_binding.py +75 -0
  156. data_engine/ui/gui/presenters/workspace_settings.py +182 -0
  157. data_engine/ui/gui/preview_models.py +37 -0
  158. data_engine/ui/gui/render_support.py +241 -0
  159. data_engine/ui/gui/rendering/__init__.py +12 -0
  160. data_engine/ui/gui/rendering/artifacts.py +95 -0
  161. data_engine/ui/gui/rendering/icons.py +50 -0
  162. data_engine/ui/gui/runtime.py +47 -0
  163. data_engine/ui/gui/state_support.py +193 -0
  164. data_engine/ui/gui/support.py +214 -0
  165. data_engine/ui/gui/surface.py +209 -0
  166. data_engine/ui/gui/theme.py +720 -0
  167. data_engine/ui/gui/widgets/__init__.py +34 -0
  168. data_engine/ui/gui/widgets/config.py +41 -0
  169. data_engine/ui/gui/widgets/logs.py +62 -0
  170. data_engine/ui/gui/widgets/panels.py +507 -0
  171. data_engine/ui/gui/widgets/sidebar.py +130 -0
  172. data_engine/ui/gui/widgets/steps.py +84 -0
  173. data_engine/ui/tui/__init__.py +5 -0
  174. data_engine/ui/tui/app.py +222 -0
  175. data_engine/ui/tui/bootstrap.py +475 -0
  176. data_engine/ui/tui/bootstrapper.py +117 -0
  177. data_engine/ui/tui/controllers/__init__.py +6 -0
  178. data_engine/ui/tui/controllers/flows.py +349 -0
  179. data_engine/ui/tui/controllers/runtime.py +167 -0
  180. data_engine/ui/tui/runtime.py +34 -0
  181. data_engine/ui/tui/state_support.py +141 -0
  182. data_engine/ui/tui/support.py +63 -0
  183. data_engine/ui/tui/theme.py +204 -0
  184. data_engine/ui/tui/widgets.py +123 -0
  185. data_engine/views/__init__.py +109 -0
  186. data_engine/views/actions.py +80 -0
  187. data_engine/views/artifacts.py +58 -0
  188. data_engine/views/flow_display.py +69 -0
  189. data_engine/views/logs.py +54 -0
  190. data_engine/views/models.py +96 -0
  191. data_engine/views/presentation.py +133 -0
  192. data_engine/views/runs.py +62 -0
  193. data_engine/views/state.py +39 -0
  194. data_engine/views/status.py +13 -0
  195. data_engine/views/text.py +109 -0
  196. py_data_engine-0.1.0.dist-info/METADATA +330 -0
  197. py_data_engine-0.1.0.dist-info/RECORD +200 -0
  198. py_data_engine-0.1.0.dist-info/WHEEL +5 -0
  199. py_data_engine-0.1.0.dist-info/entry_points.txt +2 -0
  200. py_data_engine-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,87 @@
1
+ """Theme and icon helper functions for the desktop GUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from PySide6.QtGui import QColor, QIcon, QPixmap
8
+ from PySide6.QtWidgets import QApplication
9
+
10
+ from data_engine.ui.gui.rendering import render_svg_icon_pixmap as render_svg_icon_pixmap_helper
11
+ from data_engine.ui.gui.theme import stylesheet
12
+
13
+ if TYPE_CHECKING:
14
+ from data_engine.ui.gui.app import DataEngineWindow
15
+
16
+
17
+ def group_icon(window: "DataEngineWindow", group_name: str) -> QIcon:
18
+ del group_name
19
+ return QIcon(window._render_svg_icon_pixmap("group", 18))
20
+
21
+
22
+ def group_icon_color(window: "DataEngineWindow") -> QColor:
23
+ return QColor(window.theme_service.palette(window.theme_name).text)
24
+
25
+
26
+ def render_svg_icon_pixmap(
27
+ window: "DataEngineWindow",
28
+ icon_name: str,
29
+ size: int,
30
+ *,
31
+ fill_color: str | None = None,
32
+ ) -> QPixmap:
33
+ return render_svg_icon_pixmap_helper(
34
+ icon_name=icon_name,
35
+ size=size,
36
+ device_pixel_ratio=window.devicePixelRatioF(),
37
+ fill_color=fill_color,
38
+ default_fill_color=window._group_icon_color(),
39
+ )
40
+
41
+
42
+ def view_rail_icon(window: "DataEngineWindow", view_name: str) -> QIcon:
43
+ icon_name = window._VIEW_RAIL_ICON_NAMES[view_name]
44
+ return QIcon(window._render_svg_icon_pixmap(icon_name, 18))
45
+
46
+
47
+ def action_bar_icon(window: "DataEngineWindow", action_name: str) -> QIcon:
48
+ icon_name = window._ACTION_ICON_NAMES[action_name]
49
+ return QIcon(window._render_svg_icon_pixmap(icon_name, 16))
50
+
51
+
52
+ def log_icon(window: "DataEngineWindow", icon_name: str, size: int = 16) -> QIcon:
53
+ resolved_name = window._LOG_ICON_NAMES[icon_name]
54
+ fill_color = window._LOG_ICON_COLORS.get(icon_name)
55
+ return QIcon(window._render_svg_icon_pixmap(resolved_name, size, fill_color=fill_color))
56
+
57
+
58
+ def render_group_icon_pixmap(window: "DataEngineWindow", group_name: str, size: int) -> QPixmap:
59
+ del group_name
60
+ return window._render_svg_icon_pixmap("group", size)
61
+
62
+
63
+ def toggle_theme(window: "DataEngineWindow") -> None:
64
+ window.theme_name = window.theme_service.toggle_name(window.theme_name)
65
+ window._apply_theme()
66
+
67
+
68
+ def sync_theme_to_system(window: "DataEngineWindow", *args) -> None:
69
+ del args
70
+ window.theme_name = window.theme_service.system_name()
71
+ window._apply_theme()
72
+
73
+
74
+ def apply_theme(window: "DataEngineWindow") -> None:
75
+ app = QApplication.instance()
76
+ if app is not None:
77
+ app.setStyleSheet(stylesheet(window.theme_name))
78
+ window.theme_toggle_button.setIcon(window._action_bar_icon("theme_toggle"))
79
+ window.refresh_button.setIcon(window._action_bar_icon("refresh"))
80
+ for button in (window.home_button, window.docs_button, window.settings_button):
81
+ icon_name = button.property("viewIconName")
82
+ if isinstance(icon_name, str):
83
+ button.setIcon(window._view_rail_icon(icon_name))
84
+ if window.flow_cards:
85
+ window._populate_flow_tree()
86
+ window._refresh_action_buttons()
87
+ window._refresh_log_view()
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="800px" height="800px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
4
+ <title>ic_fluent_dark_theme_24_filled</title>
5
+ <desc>Created with Sketch.</desc>
6
+ <g id="🔍-Product-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
7
+ <g id="ic_fluent_dark_theme_24_filled" fill="#212121" fill-rule="nonzero">
8
+ <path d="M12,22 C17.5228475,22 22,17.5228475 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,17.5228475 6.4771525,22 12,22 Z M12,20 L12,4 C16.418278,4 20,7.581722 20,12 C20,16.418278 16.418278,20 12,20 Z" id="🎨-Color">
9
+
10
+ </g>
11
+ </g>
12
+ </svg>
@@ -0,0 +1 @@
1
+ <svg id="Layer_1" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m17 14a1 1 0 0 1 -1 1h-8a1 1 0 0 1 0-2h8a1 1 0 0 1 1 1zm-4 3h-5a1 1 0 0 0 0 2h5a1 1 0 0 0 0-2zm9-6.515v8.515a5.006 5.006 0 0 1 -5 5h-10a5.006 5.006 0 0 1 -5-5v-14a5.006 5.006 0 0 1 5-5h4.515a6.958 6.958 0 0 1 4.95 2.05l3.484 3.486a6.951 6.951 0 0 1 2.051 4.949zm-6.949-7.021a5.01 5.01 0 0 0 -1.051-.78v4.316a1 1 0 0 0 1 1h4.316a4.983 4.983 0 0 0 -.781-1.05zm4.949 7.021c0-.165-.032-.323-.047-.485h-4.953a3 3 0 0 1 -3-3v-4.953c-.162-.015-.321-.047-.485-.047h-4.515a3 3 0 0 0 -3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3z"/></svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path d="M23.64 18.1 14.24 2.28C13.77 1.48 12.94 1 12 1s-1.77.48-2.23 1.28L.36 18.1h0c-.47.82-.47 1.79 0 2.6S1.67 22 2.6 22h18.81c.94 0 1.78-.49 2.24-1.3s.46-1.78-.01-2.6ZM13 18h-2v-2h2v2Zm0-4h-2V8h2v6Z"/>
3
+ </svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
3
+ <path d="M1.001,10.983c.062,.011,.122,.017,.182,.017,.474,0,.895-.337,.982-.82,.162-.881,.443-1.729,.833-2.532,.457,.219,.962,.352,1.502,.352,1.93,0,3.5-1.57,3.5-3.5,0-.54-.133-1.046-.353-1.503,.803-.389,1.652-.671,2.533-.833,.543-.1,.902-.621,.803-1.164-.1-.542-.61-.903-1.164-.803-1.258,.231-2.459,.668-3.58,1.284-.515-.298-1.104-.481-1.74-.481-1.93,0-3.5,1.57-3.5,3.5,0,.636,.183,1.225,.481,1.74-.615,1.121-1.052,2.321-1.283,3.58-.1,.543,.26,1.064,.803,1.164ZM4.5,3c.827,0,1.5,.673,1.5,1.5s-.673,1.5-1.5,1.5-1.5-.673-1.5-1.5,.673-1.5,1.5-1.5Zm9.328-.852c.915,.159,1.793,.447,2.625,.85-.219,.457-.352,.962-.352,1.501,0,1.93,1.57,3.5,3.5,3.5,.539,0,1.045-.133,1.502-.352,.389,.803,.672,1.65,.833,2.533,.089,.482,.509,.819,.982,.819,.06,0,.121-.005,.182-.017,.543-.1,.902-.621,.803-1.164-.232-1.26-.668-2.461-1.283-3.58,.298-.514,.481-1.103,.481-1.739,0-1.93-1.57-3.5-3.5-3.5-.636,0-1.226,.184-1.74,.482-1.154-.633-2.391-1.078-3.689-1.304-.551-.093-1.062,.27-1.157,.814-.095,.544,.27,1.062,.813,1.157Zm7.273,2.352c0,.827-.673,1.5-1.5,1.5s-1.5-.673-1.5-1.5,.673-1.5,1.5-1.5,1.5,.673,1.5,1.5ZM10.181,21.835c-.882-.162-1.73-.443-2.533-.833,.219-.457,.353-.963,.353-1.503,0-1.93-1.57-3.5-3.5-3.5-.54,0-1.045,.133-1.502,.352-.39-.803-.671-1.651-.833-2.532-.099-.543-.611-.904-1.164-.803-.543,.1-.902,.621-.803,1.164,.231,1.259,.668,2.459,1.283,3.58-.298,.515-.481,1.104-.481,1.74,0,1.93,1.57,3.5,3.5,3.5,.636,0,1.225-.183,1.74-.481,1.121,.615,2.322,1.053,3.58,1.284,.062,.011,.122,.017,.182,.017,.474,0,.894-.337,.982-.819,.1-.543-.26-1.064-.803-1.164Zm-7.181-2.335c0-.827,.673-1.5,1.5-1.5s1.5,.673,1.5,1.5-.673,1.5-1.5,1.5-1.5-.673-1.5-1.5Zm20.101-6.483c-.543-.101-1.063,.26-1.164,.803-.162,.882-.444,1.73-.833,2.533-.457-.219-.962-.352-1.502-.352-1.93,0-3.5,1.57-3.5,3.5,0,.539,.133,1.045,.352,1.501-.832,.403-1.711,.691-2.625,.85-.544,.095-.908,.613-.813,1.157,.085,.486,.507,.829,.984,.829,.057,0,.114-.005,.173-.015,1.298-.226,2.535-.671,3.689-1.304,.515,.298,1.104,.482,1.74,.482,1.93,0,3.5-1.57,3.5-3.5,0-.636-.183-1.224-.481-1.739,.615-1.12,1.051-2.32,1.283-3.58,.1-.543-.26-1.064-.803-1.164Zm-3.499,7.983c-.827,0-1.5-.673-1.5-1.5s.673-1.5,1.5-1.5,1.5,.673,1.5,1.5-.673,1.5-1.5,1.5Zm-7.602-3v.993c0,.583-.688,.893-1.125,.508l-2.01-1.773c-.487-.42-.487-1.155,0-1.575l2.01-1.773c.437-.385,1.125-.075,1.125,.508v1.112c2.206,0,4-1.794,4-4,0-.557-.112-1.095-.332-1.6-.222-.506,.01-1.096,.516-1.317,.508-.221,1.097,.011,1.316,.516,.332,.759,.5,1.566,.5,2.4,0,3.309-2.691,6-6,6Zm0-12v-.993c0-.583,.687-.893,1.125-.508l2.01,1.773c.487,.42,.487,1.155,0,1.575l-2.01,1.773c-.437,.385-1.125,.075-1.125-.508v-1.111c-2.206,0-4,1.794-4,4,0,.557,.112,1.095,.332,1.6,.222,.506-.01,1.096-.516,1.317-.13,.057-.267,.084-.399,.084-.386,0-.753-.224-.917-.6-.332-.759-.5-1.566-.5-2.4,0-3.309,2.691-6,6-6Z"/>
4
+ </svg>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" id="Outline" viewBox="0 0 24 24" width="512" height="512"><path d="M23.121,9.069,15.536,1.483a5.008,5.008,0,0,0-7.072,0L.879,9.069A2.978,2.978,0,0,0,0,11.19v9.817a3,3,0,0,0,3,3H21a3,3,0,0,0,3-3V11.19A2.978,2.978,0,0,0,23.121,9.069ZM15,22.007H9V18.073a3,3,0,0,1,6,0Zm7-1a1,1,0,0,1-1,1H17V18.073a5,5,0,0,0-10,0v3.934H3a1,1,0,0,1-1-1V11.19a1.008,1.008,0,0,1,.293-.707L9.878,2.9a3.008,3.008,0,0,1,4.244,0l7.585,7.586A1.008,1.008,0,0,1,22,11.19Z"/></svg>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" id="Bold" viewBox="0 0 24 24" width="512" height="512"><path d="M20.463,7.713l-9.1-6.677A5.317,5.317,0,0,0,2.9,5.323V18.677a5.311,5.311,0,0,0,8.46,4.287l9.105-6.677a5.315,5.315,0,0,0,0-8.574Zm-1.774,6.155-9.1,6.677A2.317,2.317,0,0,1,5.9,18.677V5.323a2.276,2.276,0,0,1,1.27-2.066A2.328,2.328,0,0,1,8.223,3a2.3,2.3,0,0,1,1.362.455l9.1,6.677a2.316,2.316,0,0,1,0,3.736Z"/></svg>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="M7,0h-3C1.79,0,0,1.79,0,4v3c0,2.21,1.79,4,4,4h3c2.21,0,4-1.79,4-4v-3C11,1.79,9.21,0,7,0Zm2,7c0,1.1-.9,2-2,2h-3c-1.1,0-2-.9-2-2v-3c0-1.1,.9-2,2-2h3c1.1,0,2,.9,2,2v3Zm11,6h-3c-2.21,0-4,1.79-4,4v3c0,2.21,1.79,4,4,4h3c2.21,0,4-1.79,4-4v-3c0-2.21-1.79-4-4-4Zm2,7c0,1.1-.9,2-2,2h-3c-1.1,0-2-.9-2-2v-3c0-1.1,.9-2,2-2h3c1.1,0,2,.9,2,2v3ZM13,5c0-1.06,.44-2.05,1.25-2.78l2.07-1.95c.4-.38,1.03-.36,1.41,.04,.38,.4,.36,1.04-.04,1.41l-2.08,1.95c-.11,.1-.2,.21-.28,.32h3.67c2.21,0,4,1.79,4,4v2c0,.55-.45,1-1,1s-1-.45-1-1v-2c0-1.1-.9-2-2-2h-3.67c.08,.11,.17,.21,.27,.31l2.09,1.96c.4,.38,.42,1.01,.04,1.41-.2,.21-.46,.31-.73,.31-.25,0-.49-.09-.69-.27l-2.08-1.95c-.79-.73-1.24-1.72-1.24-2.77Zm-2,14c0,1.06-.44,2.04-1.25,2.78l-2.07,1.95c-.19,.18-.44,.27-.69,.27-.27,0-.53-.11-.73-.31-.38-.4-.36-1.04,.04-1.41l2.08-1.95c.11-.1,.2-.21,.28-.32h-3.67c-2.21,0-4-1.79-4-4v-2c0-.55,.45-1,1-1s1,.45,1,1v2c0,1.1,.9,2,2,2h3.67c-.08-.11-.17-.21-.27-.31l-2.09-1.96c-.4-.38-.42-1.01-.04-1.41,.38-.4,1.01-.42,1.41-.04l2.08,1.95c.79,.73,1.24,1.72,1.24,2.77Z"/></svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
3
+ <path d="M19.5,2h-1.5V.5c0-.276-.224-.5-.5-.5s-.5,.224-.5,.5v1.5H7V.5c0-.276-.224-.5-.5-.5s-.5,.224-.5,.5v1.5h-1.5C2.019,2,0,4.019,0,6.5v13c0,2.481,2.019,4.5,4.5,4.5h15c2.481,0,4.5-2.019,4.5-4.5V6.5c0-2.481-2.019-4.5-4.5-4.5ZM4.5,3h15c1.93,0,3.5,1.57,3.5,3.5v1.5H1v-1.5c0-1.93,1.57-3.5,3.5-3.5Zm15,20H4.5c-1.93,0-3.5-1.57-3.5-3.5V9H23v10.5c0,1.93-1.57,3.5-3.5,3.5Zm-2.5-11.5v2.5c0,.552-.448,1-1,1h-2.5c-.276,0-.5-.224-.5-.5s.224-.5,.5-.5h1.957c-.706-1.217-2.015-2-3.457-2-1.692,0-3.209,1.072-3.772,2.667-.093,.262-.375,.398-.639,.305-.26-.092-.396-.377-.305-.638,.705-1.994,2.601-3.333,4.716-3.333,1.6,0,3.072,.768,4,2.004v-1.504c0-.276,.224-.5,.5-.5s.5,.224,.5,.5Zm-.284,6.167c-.705,1.994-2.601,3.333-4.716,3.333-1.6,0-3.072-.768-4-2.004v1.504c0,.276-.224,.5-.5,.5s-.5-.224-.5-.5v-2.5c0-.552,.448-1,1-1h2.5c.276,0,.5,.224,.5,.5s-.224,.5-.5,.5h-1.957c.706,1.217,2.015,2,3.457,2,1.692,0,3.209-1.072,3.772-2.667,.093-.261,.377-.396,.639-.305,.26,.092,.396,.377,.305,.638Z"/>
4
+ </svg>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" id="Outline" viewBox="0 0 24 24" width="512" height="512"><path d="M12,8a4,4,0,1,0,4,4A4,4,0,0,0,12,8Zm0,6a2,2,0,1,1,2-2A2,2,0,0,1,12,14Z"/><path d="M21.294,13.9l-.444-.256a9.1,9.1,0,0,0,0-3.29l.444-.256a3,3,0,1,0-3-5.2l-.445.257A8.977,8.977,0,0,0,15,3.513V3A3,3,0,0,0,9,3v.513A8.977,8.977,0,0,0,6.152,5.159L5.705,4.9a3,3,0,0,0-3,5.2l.444.256a9.1,9.1,0,0,0,0,3.29l-.444.256a3,3,0,1,0,3,5.2l.445-.257A8.977,8.977,0,0,0,9,20.487V21a3,3,0,0,0,6,0v-.513a8.977,8.977,0,0,0,2.848-1.646l.447.258a3,3,0,0,0,3-5.2Zm-2.548-3.776a7.048,7.048,0,0,1,0,3.75,1,1,0,0,0,.464,1.133l1.084.626a1,1,0,0,1-1,1.733l-1.086-.628a1,1,0,0,0-1.215.165,6.984,6.984,0,0,1-3.243,1.875,1,1,0,0,0-.751.969V21a1,1,0,0,1-2,0V19.748a1,1,0,0,0-.751-.969A6.984,6.984,0,0,1,7.006,16.9a1,1,0,0,0-1.215-.165l-1.084.627a1,1,0,1,1-1-1.732l1.084-.626a1,1,0,0,0,.464-1.133,7.048,7.048,0,0,1,0-3.75A1,1,0,0,0,4.79,8.992L3.706,8.366a1,1,0,0,1,1-1.733l1.086.628A1,1,0,0,0,7.006,7.1a6.984,6.984,0,0,1,3.243-1.875A1,1,0,0,0,11,4.252V3a1,1,0,0,1,2,0V4.252a1,1,0,0,0,.751.969A6.984,6.984,0,0,1,16.994,7.1a1,1,0,0,0,1.215.165l1.084-.627a1,1,0,1,1,1,1.732l-1.084.626A1,1,0,0,0,18.746,10.125Z"/></svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 513.806 513.806">
2
+ <path d="M66.074 228.731C81.577 123.379 179.549 50.542 284.901 66.045c35.944 5.289 69.662 20.626 97.27 44.244l-24.853 24.853c-8.33 8.332-8.328 21.84.005 30.17 3.999 3.998 9.423 6.245 15.078 6.246h97.835c11.782 0 21.333-9.551 21.333-21.333V52.39c-.003-11.782-9.556-21.331-21.338-21.329-5.655.001-11.079 2.248-15.078 6.246L427.418 65.04C321.658-29.235 159.497-19.925 65.222 85.835c-33.399 37.467-55.073 83.909-62.337 133.573-2.864 17.607 9.087 34.202 26.693 37.066 1.586.258 3.188.397 4.795.417 16.108-.174 29.629-12.185 31.701-28.16ZM479.429 256.891c-16.108.174-29.629 12.185-31.701 28.16C432.225 390.403 334.253 463.24 228.901 447.738c-35.944-5.289-69.662-20.626-97.27-44.244l24.853-24.853c8.33-8.332 8.328-21.84-.005-30.17-3.999-3.998-9.423-6.245-15.078-6.246H43.568c-11.782 0-21.333 9.551-21.333 21.333v97.835c.003 11.782 9.556 21.331 21.338 21.329 5.655-.001 11.079-2.248 15.078-6.246l27.733-27.733c105.735 94.285 267.884 85.004 362.17-20.732 33.417-37.475 55.101-83.933 62.363-133.615 2.876-17.605-9.064-34.208-26.668-37.084-1.592-.26-3.203-.401-4.818-.42Z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path d="m12 1c-7.71 0-11 3.29-11 11s3.29 11 11 11 11-3.29 11-11-3.29-11-11-11zm5.305 8.539c-1.312 2.053-3.18 4.626-6.001 6.319-.324.195-.731.19-1.05-.013-1.52-.963-2.661-1.995-3.594-3.248-.33-.443-.238-1.069.205-1.399.442-.33 1.069-.237 1.398.205.674.905 1.488 1.679 2.536 2.405 2.16-1.46 3.644-3.507 4.819-5.346.299-.466.917-.602 1.381-.304.466.298.602.916.305 1.381z"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path d="m14 7v-6.54c.913.346 1.753.879 2.465 1.59l3.484 3.486c.712.711 1.245 1.551 1.591 2.464h-6.54c-.552 0-1-.449-1-1zm-1.771 7.985c0-.394-.342-.726-.75-.733-.402.007-.725.333-.724.736 0 .095.008 1.886.008 2.024 0 .403.322.729.724.736.408-.007.75-.338.75-.733l-.008-2.015zm9.747-4.985h-6.976c-1.654 0-3-1.346-3-3v-6.976c-.161-.011-.322-.024-.485-.024h-4.515c-2.757 0-5 2.243-5 5v14c0 2.757 2.243 5 5 5h10c2.757 0 5-2.243 5-5v-8.515c0-.163-.013-.324-.024-.485zm-13.254 8.368c0 .346-.28.627-.626.627l-1.468.003c-.347 0-.629-.28-.629-.627l-.005-4.744c0-.347.281-.628.628-.628s.627.28.627.626c.001 1.249.003 2.869.004 4.118.71-.001.592-.003.837-.004.348-.002.631.28.631.628zm4.765-1.353c0 1.094-.902 1.985-2.012 1.985-1.086 0-1.967-.881-1.967-1.967l-.008-2.066c0-1.086.881-1.967 1.967-1.967 1.109 0 2.012.891 2.012 1.985v.015l.008 2zm5 0c0 1.094-.902 1.985-2.012 1.985-1.086 0-1.967-.881-1.967-1.967l-.008-2.066c0-1.086.881-1.967 1.967-1.967.772 0 1.445.432 1.782 1.064.223.417-.08.921-.553.921-.228 0-.441-.122-.548-.324-.125-.237-.38-.404-.669-.409-.402.007-.725.333-.724.736 0 .095.008 1.886.008 2.024 0 .403.322.729.724.736.32-.005.593-.212.698-.491-.036 0-.063 0-.085 0-.349.004-.633-.278-.633-.627 0-.346.28-.626.625-.627l.766-.002c.346-.001.627.278.628.624v.374s.001.015.001.015z"/>
3
+ </svg>
@@ -0,0 +1,50 @@
1
+ """UI icon registry and loading helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from importlib import resources
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class SvgIconAsset:
11
+ """Describe one SVG icon source."""
12
+
13
+ file_name: str | None = None
14
+ svg_text: str | None = None
15
+
16
+ def read_text(self) -> str:
17
+ """Return raw SVG text for this asset."""
18
+ if self.svg_text is not None:
19
+ return self.svg_text
20
+ if self.file_name is None:
21
+ raise ValueError("SVG icon asset has no source configured.")
22
+ return resources.files("data_engine.ui.gui").joinpath("icons", self.file_name).read_text(encoding="utf-8")
23
+
24
+
25
+ ICON_ASSETS: dict[str, SvgIconAsset] = {
26
+ "dark_light": SvgIconAsset(file_name="dark_light.svg"),
27
+ "documentation": SvgIconAsset(file_name="documentation.svg"),
28
+ "failed": SvgIconAsset(file_name="failed.svg"),
29
+ "group": SvgIconAsset(file_name="group.svg"),
30
+ "home": SvgIconAsset(file_name="home.svg"),
31
+ "manual": SvgIconAsset(file_name="manual.svg"),
32
+ "poll": SvgIconAsset(file_name="poll.svg"),
33
+ "schedule": SvgIconAsset(file_name="schedule.svg"),
34
+ "settings": SvgIconAsset(file_name="settings.svg"),
35
+ "started": SvgIconAsset(file_name="started.svg"),
36
+ "success": SvgIconAsset(file_name="success.svg"),
37
+ "view-log": SvgIconAsset(file_name="view-log.svg"),
38
+ }
39
+
40
+
41
+ def load_svg_icon_text(icon_name: str) -> str:
42
+ """Return SVG text for one registered icon."""
43
+ try:
44
+ asset = ICON_ASSETS[icon_name]
45
+ except KeyError as exc:
46
+ raise KeyError(f"Unknown UI icon: {icon_name}") from exc
47
+ return asset.read_text()
48
+
49
+
50
+ __all__ = ["ICON_ASSETS", "SvgIconAsset", "load_svg_icon_text"]
@@ -0,0 +1,48 @@
1
+ """Launcher entrypoints for the Data Engine PySide6 UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ from PySide6.QtWidgets import QApplication
8
+
9
+ from data_engine.platform.identity import APP_DISPLAY_NAME
10
+ from data_engine.ui.gui.bootstrap import build_gui_services
11
+ from data_engine.ui.gui.app import DataEngineWindow
12
+ from data_engine.ui.gui.theme import DEFAULT_THEME, resolve_theme_name, stylesheet
13
+
14
+
15
+ def _configure_qt_webengine_environment() -> None:
16
+ """Set stable Chromium flags before the UI spins up embedded docs."""
17
+ existing = os.environ.get("QTWEBENGINE_CHROMIUM_FLAGS", "").strip()
18
+ flags = [flag for flag in existing.split() if flag]
19
+ if "--disable-skia-graphite" not in flags:
20
+ flags.append("--disable-skia-graphite")
21
+ os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = " ".join(flags)
22
+
23
+
24
+ def launch(theme_name: str = DEFAULT_THEME) -> None:
25
+ """Create and run the PySide6 application."""
26
+ _configure_qt_webengine_environment()
27
+ services = build_gui_services()
28
+ app = QApplication.instance() or QApplication([])
29
+ app.setApplicationName(APP_DISPLAY_NAME)
30
+ app.setStyle("Fusion")
31
+ resolved_theme = services.theme_service.resolve_name(theme_name)
32
+ app.setStyleSheet(stylesheet(resolved_theme))
33
+ window = DataEngineWindow(theme_name=resolved_theme, services=services)
34
+ window.show()
35
+ if QApplication.instance() is app:
36
+ app.exec()
37
+
38
+
39
+ def main() -> None:
40
+ """Console entrypoint for the desktop UI."""
41
+ launch()
42
+
43
+
44
+ if __name__ == "__main__":
45
+ main()
46
+
47
+
48
+ __all__ = ["launch", "main"]
@@ -0,0 +1,72 @@
1
+ """Presenter helpers for the desktop UI."""
2
+
3
+ from data_engine.ui.gui.presenters.logs import add_log_run_item, format_raw_log_message, refresh_log_view
4
+ from data_engine.ui.gui.presenters.docs import (
5
+ create_docs_browser,
6
+ docs_build_dir,
7
+ finish_docs_build,
8
+ initialize_docs_view,
9
+ load_docs_page,
10
+ run_docs_build_worker,
11
+ start_docs_build,
12
+ )
13
+ from data_engine.ui.gui.presenters.runtime_projection import (
14
+ apply_daemon_snapshot,
15
+ finish_daemon_startup,
16
+ )
17
+ from data_engine.ui.gui.presenters.sidebar import refresh_sidebar_selection, refresh_sidebar_state_views, repolish_widget_tree, set_hovered
18
+ from data_engine.ui.gui.presenters.steps import (
19
+ apply_runtime_event,
20
+ duration_text,
21
+ format_seconds,
22
+ normalize_completed_operation_rows,
23
+ refresh_live_operation_durations,
24
+ render_operation_durations,
25
+ reset_operation_state,
26
+ )
27
+ from data_engine.ui.gui.presenters.workspace_binding import rebind_workspace_context
28
+ from data_engine.ui.gui.presenters.workspace_settings import (
29
+ browse_workspace_collection_root_override,
30
+ force_shutdown_daemon,
31
+ provision_selected_workspace,
32
+ refresh_workspace_provisioning_controls,
33
+ refresh_workspace_visibility_panel,
34
+ refresh_workspace_root_controls,
35
+ reset_workspace_collection_root_override,
36
+ save_workspace_collection_root_override,
37
+ )
38
+
39
+ __all__ = [
40
+ "add_log_run_item",
41
+ "apply_runtime_event",
42
+ "apply_daemon_snapshot",
43
+ "browse_workspace_collection_root_override",
44
+ "create_docs_browser",
45
+ "docs_build_dir",
46
+ "duration_text",
47
+ "finish_daemon_startup",
48
+ "finish_docs_build",
49
+ "format_raw_log_message",
50
+ "format_seconds",
51
+ "force_shutdown_daemon",
52
+ "initialize_docs_view",
53
+ "load_docs_page",
54
+ "normalize_completed_operation_rows",
55
+ "rebind_workspace_context",
56
+ "provision_selected_workspace",
57
+ "refresh_log_view",
58
+ "refresh_workspace_provisioning_controls",
59
+ "refresh_workspace_visibility_panel",
60
+ "refresh_live_operation_durations",
61
+ "refresh_workspace_root_controls",
62
+ "refresh_sidebar_selection",
63
+ "refresh_sidebar_state_views",
64
+ "reset_workspace_collection_root_override",
65
+ "render_operation_durations",
66
+ "repolish_widget_tree",
67
+ "reset_operation_state",
68
+ "run_docs_build_worker",
69
+ "save_workspace_collection_root_override",
70
+ "set_hovered",
71
+ "start_docs_build",
72
+ ]
@@ -0,0 +1,140 @@
1
+ """Documentation browser/build helpers for the desktop UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from pathlib import Path
7
+ import subprocess
8
+ import sys
9
+ from typing import TYPE_CHECKING
10
+
11
+ from PySide6.QtCore import QUrl
12
+ from PySide6.QtWidgets import QFrame, QTextBrowser
13
+ from PySide6.QtWebEngineWidgets import QWebEngineView
14
+
15
+ from data_engine.ui.gui.helpers import start_worker_thread
16
+
17
+ if TYPE_CHECKING:
18
+ from data_engine.ui.gui.app import DataEngineWindow
19
+
20
+
21
+ def _explicit_missing_error_detail(subject: str) -> str:
22
+ return f"{subject} did not provide any additional error details."
23
+
24
+
25
+ def create_docs_browser(window: "DataEngineWindow"):
26
+ if os.environ.get("QT_QPA_PLATFORM") != "offscreen":
27
+ window.docs_uses_webengine = True
28
+ browser = QWebEngineView()
29
+ browser.setStyleSheet("background: #ffffff;")
30
+ return browser
31
+ window.docs_uses_webengine = False
32
+ browser = QTextBrowser()
33
+ browser.setOpenExternalLinks(True)
34
+ browser.setFrameShape(QFrame.Shape.NoFrame)
35
+ return browser
36
+
37
+
38
+ def docs_build_dir(window: "DataEngineWindow") -> Path:
39
+ return window.workspace_paths.documentation_dir / "_build" / "html"
40
+
41
+
42
+ def initialize_docs_view(window: "DataEngineWindow") -> None:
43
+ docs_root = docs_build_dir(window)
44
+ window.docs_root_dir = docs_root if docs_root.is_dir() else None
45
+ if window.docs_root_dir is None:
46
+ window.docs_status_label.setText("Built documentation is not available yet.")
47
+ window.docs_generate_button.setVisible(True)
48
+ window.docs_generate_button.setEnabled(not window.docs_build_running)
49
+ if window.docs_build_running:
50
+ window.docs_browser.setHtml("<h2>Building documentation</h2><p>Generating the in-app docs site...</p>")
51
+ else:
52
+ window.docs_browser.setHtml(
53
+ "<h2>Documentation unavailable</h2><p>Generate the Sphinx site to populate the in-app docs view.</p>"
54
+ )
55
+ return
56
+
57
+ window.docs_generate_button.setVisible(False)
58
+ window.docs_generate_button.setEnabled(True)
59
+ load_docs_page(window, window._DOCS_HOME_PAGE)
60
+
61
+
62
+ def start_docs_build(window: "DataEngineWindow") -> None:
63
+ if window.docs_build_running:
64
+ return
65
+ window.docs_build_running = True
66
+ initialize_docs_view(window)
67
+ start_worker_thread(window, target=window._run_docs_build_worker)
68
+
69
+
70
+ def run_docs_build_worker(window: "DataEngineWindow") -> None:
71
+ build_dir = docs_build_dir(window)
72
+ try:
73
+ build_dir.parent.mkdir(parents=True, exist_ok=True)
74
+ completed = subprocess.run(
75
+ [
76
+ sys.executable,
77
+ "-m",
78
+ "sphinx",
79
+ "-b",
80
+ "html",
81
+ str(window.workspace_paths.sphinx_source_dir),
82
+ str(build_dir),
83
+ ],
84
+ capture_output=True,
85
+ text=True,
86
+ check=True,
87
+ )
88
+ message = completed.stdout.strip() or "Documentation build completed."
89
+ window.signals.docs_build_finished.emit(True, message)
90
+ except Exception as exc:
91
+ detail = str(exc)
92
+ if isinstance(exc, subprocess.CalledProcessError):
93
+ detail = (exc.stderr or exc.stdout or str(exc)).strip()
94
+ window.signals.docs_build_finished.emit(
95
+ False,
96
+ detail or _explicit_missing_error_detail("Documentation build"),
97
+ )
98
+
99
+
100
+ def finish_docs_build(window: "DataEngineWindow", succeeded: bool, message: str) -> None:
101
+ window.docs_build_running = False
102
+ if succeeded:
103
+ initialize_docs_view(window)
104
+ window.docs_status_label.setText("index.html")
105
+ return
106
+ initialize_docs_view(window)
107
+ if not message:
108
+ message = _explicit_missing_error_detail("Documentation build")
109
+ window.docs_status_label.setText(message)
110
+ window._show_message_box_later(
111
+ title=window.windowTitle(),
112
+ text=f"Documentation build failed.\n\n{message}",
113
+ tone="error",
114
+ )
115
+
116
+
117
+ def load_docs_page(window: "DataEngineWindow", file_name: str) -> None:
118
+ if window.docs_root_dir is None:
119
+ return
120
+ target = window.docs_root_dir / file_name
121
+ if not target.exists():
122
+ window.docs_status_label.setText(f"Missing page: {file_name}")
123
+ window.docs_browser.setHtml(f"<h2>Missing documentation page</h2><p>{file_name}</p>")
124
+ return
125
+ window.docs_status_label.setText(target.name)
126
+ if window.docs_uses_webengine:
127
+ window.docs_browser.setUrl(QUrl.fromLocalFile(str(target)))
128
+ else:
129
+ window.docs_browser.setSource(QUrl.fromLocalFile(str(target)))
130
+
131
+
132
+ __all__ = [
133
+ "create_docs_browser",
134
+ "docs_build_dir",
135
+ "finish_docs_build",
136
+ "initialize_docs_view",
137
+ "load_docs_page",
138
+ "run_docs_build_worker",
139
+ "start_docs_build",
140
+ ]
@@ -0,0 +1,58 @@
1
+ """Log-list presentation helpers for the desktop UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from PySide6.QtWidgets import QListWidgetItem
8
+
9
+ from data_engine.views import format_raw_log_message as shared_format_raw_log_message
10
+ from data_engine.ui.gui.widgets.logs import build_log_run_widget
11
+
12
+ if TYPE_CHECKING:
13
+ from data_engine.domain import FlowLogEntry, FlowRunState
14
+ from data_engine.ui.gui.app import DataEngineWindow
15
+
16
+
17
+ def refresh_log_view(window: "DataEngineWindow", *, force_scroll_to_bottom: bool = False) -> None:
18
+ scrollbar = window.log_view.verticalScrollBar()
19
+ previous_value = scrollbar.value()
20
+ previous_maximum = scrollbar.maximum()
21
+ should_follow_tail = force_scroll_to_bottom or previous_value >= max(previous_maximum - 1, 0)
22
+
23
+ window.log_view.setUpdatesEnabled(False)
24
+ window.log_view.clear()
25
+ card = window.flow_cards.get(window.selected_flow_name or "")
26
+ run_groups = window.log_service.runs_for_flow(window.runtime_binding.log_store, card.name) if card is not None else ()
27
+ presentation = window.detail_application.build_selected_flow_presentation(
28
+ card=card,
29
+ tracker=window.operation_tracker,
30
+ flow_states=window.flow_states,
31
+ run_groups=tuple(run_groups),
32
+ selected_run_key=None,
33
+ max_visible_runs=window._MAX_VISIBLE_LOG_RUNS,
34
+ )
35
+ for run_group in presentation.visible_run_groups:
36
+ add_log_run_item(window, run_group)
37
+ window.log_view.setUpdatesEnabled(True)
38
+
39
+ scrollbar = window.log_view.verticalScrollBar()
40
+ if should_follow_tail:
41
+ scrollbar.setValue(scrollbar.maximum())
42
+ else:
43
+ scrollbar.setValue(min(previous_value, scrollbar.maximum()))
44
+
45
+
46
+ def add_log_run_item(window: "DataEngineWindow", run_group: "FlowRunState") -> None:
47
+ item = QListWidgetItem(run_group.display_label)
48
+ widget = build_log_run_widget(window, run_group)
49
+ item.setSizeHint(widget.sizeHint())
50
+ window.log_view.addItem(item)
51
+ window.log_view.setItemWidget(item, widget)
52
+
53
+
54
+ def format_raw_log_message(entry: "FlowLogEntry") -> str:
55
+ return shared_format_raw_log_message(entry)
56
+
57
+
58
+ __all__ = ["add_log_run_item", "format_raw_log_message", "refresh_log_view"]
@@ -0,0 +1,29 @@
1
+ """Daemon/runtime projection helpers for the desktop UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from data_engine.domain import RuntimeSessionState
8
+
9
+ if TYPE_CHECKING:
10
+ from data_engine.ui.gui.app import DataEngineWindow
11
+
12
+
13
+ def finish_daemon_startup(window: "DataEngineWindow", success: bool, error_text: str) -> None:
14
+ window._daemon_startup_in_progress = False
15
+ if not success and not error_text:
16
+ error_text = "Daemon startup did not provide any additional error details."
17
+ if not success and error_text and window.isVisible():
18
+ window._append_log_line(f"Daemon startup failed: {error_text}")
19
+ window._sync_from_daemon()
20
+
21
+
22
+ def apply_daemon_snapshot(window: "DataEngineWindow", snapshot) -> None:
23
+ window.runtime_session = RuntimeSessionState.from_daemon_snapshot(snapshot, window.flow_cards.values())
24
+
25
+
26
+ __all__ = [
27
+ "apply_daemon_snapshot",
28
+ "finish_daemon_startup",
29
+ ]
@@ -0,0 +1,88 @@
1
+ """Sidebar presentation helpers for the desktop UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from PySide6.QtWidgets import QLabel, QFrame, QWidget
8
+
9
+ from data_engine.views.flow_display import FlowRowDisplay, GroupRowDisplay
10
+ from data_engine.views.presentation import flow_group_name
11
+
12
+ if TYPE_CHECKING:
13
+ from data_engine.ui.gui.app import DataEngineWindow
14
+
15
+
16
+ def refresh_sidebar_selection(window: "DataEngineWindow") -> None:
17
+ for flow_name, widget in window.sidebar_flow_widgets.items():
18
+ selected = flow_name == window.selected_flow_name
19
+ flow_index = widget.property("flowIndex")
20
+ for label in widget.findChildren(QLabel, "sidebarFlowNumber"):
21
+ if isinstance(flow_index, int):
22
+ label.setText(f"{flow_index:02d}")
23
+ if widget.property("selected") == selected:
24
+ continue
25
+ widget.setProperty("selected", selected)
26
+ repolish_widget_tree(widget)
27
+
28
+
29
+ def refresh_sidebar_state_views(window: "DataEngineWindow", changed_flow_names: set[str]) -> bool:
30
+ """Refresh sidebar labels/colors in place for state-only changes.
31
+
32
+ Return True when the caller should fall back to a full sidebar rebuild.
33
+ """
34
+ if not changed_flow_names or not window.sidebar_flow_widgets:
35
+ return True
36
+
37
+ affected_groups: set[str] = set()
38
+ for flow_name in changed_flow_names:
39
+ card = window.flow_cards.get(flow_name)
40
+ widget = window.sidebar_flow_widgets.get(flow_name)
41
+ if card is None or widget is None:
42
+ return True
43
+ affected_groups.add(flow_group_name(card))
44
+ state = window.flow_states.get(card.name, card.state)
45
+ flow_display = FlowRowDisplay.from_card(card, state, primary="name")
46
+ widget.setToolTip(flow_display.tooltip)
47
+ for label in widget.findChildren(QLabel, "sidebarFlowMeta"):
48
+ label.setText(flow_display.secondary)
49
+ label.setProperty("stateColor", flow_display.state_color)
50
+ repolish_widget_tree(label)
51
+ for label in widget.findChildren(QLabel, "sidebarStateDot"):
52
+ label.setProperty("stateColor", flow_display.state_color)
53
+ repolish_widget_tree(label)
54
+
55
+ for group_name in affected_groups:
56
+ group_widget = window.sidebar_group_widgets.get(group_name)
57
+ if group_widget is None:
58
+ return True
59
+ entries = [card for card in window.flow_cards.values() if flow_group_name(card) == group_name]
60
+ group_display = GroupRowDisplay.from_group(group_name, entries, window.flow_states)
61
+ for label in group_widget.findChildren(QLabel, "sidebarGroupMeta"):
62
+ label.setText(group_display.secondary)
63
+
64
+ return False
65
+
66
+
67
+ def set_hovered(widget: QFrame, hovered: bool) -> None:
68
+ """Update one sidebar row hover property and repolish it."""
69
+ if widget.property("hovered") == hovered:
70
+ return
71
+ widget.setProperty("hovered", hovered)
72
+ repolish_widget_tree(widget)
73
+
74
+
75
+ def repolish_widget_tree(widget: QWidget) -> None:
76
+ """Reapply stylesheet state to one widget and its child widgets."""
77
+ style = widget.style()
78
+ style.unpolish(widget)
79
+ style.polish(widget)
80
+ for child in widget.findChildren(QWidget):
81
+ child_style = child.style()
82
+ child_style.unpolish(child)
83
+ child_style.polish(child)
84
+ child.update()
85
+ widget.update()
86
+
87
+
88
+ __all__ = ["refresh_sidebar_selection", "refresh_sidebar_state_views", "repolish_widget_tree", "set_hovered"]