hexastack-ui 0.4.0__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.
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: hexastack-ui
3
+ Version: 0.4.0
4
+ Summary: Reactive UI and DevTools presentation adapters for Hexastack
5
+ Author: Richard West
6
+ Author-email: Richard West <dopplereffect.us@gmail.com>
7
+ License-Expression: Apache-2.0
8
+ Requires-Dist: hexastack-core
9
+ Requires-Dist: hexastack-cqrs
10
+ Requires-Dist: pydantic>=2.10.0
11
+ Requires-Dist: hexastack-ui[nicegui,textual] ; extra == 'all'
12
+ Requires-Dist: hexastack-ui[nicegui] ; extra == 'devtools'
13
+ Requires-Dist: fastapi>=0.141.1 ; extra == 'fastapi'
14
+ Requires-Dist: fastapi>=0.141.1 ; extra == 'nicegui'
15
+ Requires-Dist: nicegui>=2.0.0 ; extra == 'nicegui'
16
+ Requires-Dist: textual>=0.70.0 ; extra == 'textual'
17
+ Requires-Python: >=3.13
18
+ Provides-Extra: all
19
+ Provides-Extra: devtools
20
+ Provides-Extra: fastapi
21
+ Provides-Extra: nicegui
22
+ Provides-Extra: textual
23
+ Description-Content-Type: text/markdown
24
+
25
+ # hexastack-ui
26
+
27
+ Reactive UI presentation adapters and turnkey DevTools dashboard for Hexastack.
28
+
29
+ ## Overview
30
+
31
+ `hexastack-ui` provides reactive UI integration for Hexastack architectures:
32
+ - **NiceGUI Web Adapter**: `@ui_page`, `mount_ui_app`, `dispatch_command`, `dispatch_query`.
33
+ - **Interactive DevTools Console**: `mount_devtools_dashboard` inspecting CQRS registries, feature flags, and DI containers.
34
+ - **Pluggable Architecture**: Modular ports for expanding to Textual (TUI) and other frontend engines.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ # With NiceGUI web engine
40
+ pip install hexastack-ui[nicegui]
41
+
42
+ # Or via the umbrella Hexastack distribution
43
+ pip install hexastack[ui]
44
+ ```
@@ -0,0 +1,20 @@
1
+ # hexastack-ui
2
+
3
+ Reactive UI presentation adapters and turnkey DevTools dashboard for Hexastack.
4
+
5
+ ## Overview
6
+
7
+ `hexastack-ui` provides reactive UI integration for Hexastack architectures:
8
+ - **NiceGUI Web Adapter**: `@ui_page`, `mount_ui_app`, `dispatch_command`, `dispatch_query`.
9
+ - **Interactive DevTools Console**: `mount_devtools_dashboard` inspecting CQRS registries, feature flags, and DI containers.
10
+ - **Pluggable Architecture**: Modular ports for expanding to Textual (TUI) and other frontend engines.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ # With NiceGUI web engine
16
+ pip install hexastack-ui[nicegui]
17
+
18
+ # Or via the umbrella Hexastack distribution
19
+ pip install hexastack[ui]
20
+ ```
@@ -0,0 +1,50 @@
1
+ [project]
2
+ name = "hexastack-ui"
3
+ version = "0.4.0"
4
+ description = "Reactive UI and DevTools presentation adapters for Hexastack"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ requires-python = ">=3.13"
8
+ dependencies = [
9
+ "hexastack-core",
10
+ "hexastack-cqrs",
11
+ "pydantic>=2.10.0",
12
+ ]
13
+
14
+ [[project.authors]]
15
+ name = "Richard West"
16
+ email = "dopplereffect.us@gmail.com"
17
+
18
+ [project.optional-dependencies]
19
+ fastapi = ["fastapi>=0.141.1"]
20
+ nicegui = [
21
+ "fastapi>=0.141.1",
22
+ "nicegui>=2.0.0",
23
+ ]
24
+ devtools = ["hexastack-ui[nicegui]"]
25
+ textual = ["textual>=0.70.0"]
26
+ all = ["hexastack-ui[nicegui,textual]"]
27
+
28
+ [build-system]
29
+ requires = ["uv_build>=0.12.3,<0.13.0"]
30
+ build-backend = "uv_build"
31
+
32
+ [tool.uv.sources.hexastack-core]
33
+ workspace = true
34
+
35
+ [tool.uv.sources.hexastack-cqrs]
36
+ workspace = true
37
+
38
+ [tool.importlinter]
39
+ root_packages = ["hexastack_ui"]
40
+
41
+ [[tool.importlinter.contracts]]
42
+ name = "Hexagonal architecture layer hierarchy"
43
+ type = "layers"
44
+ containers = ["hexastack_ui"]
45
+ layers = [
46
+ "infra",
47
+ "adapters",
48
+ "ports",
49
+ "domain",
50
+ ]
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "hexastack-ui"
3
+ version = "0.4.0"
4
+ description = "Reactive UI and DevTools presentation adapters for Hexastack"
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ authors = [
8
+ { name = "Richard West", email = "dopplereffect.us@gmail.com" }
9
+ ]
10
+ requires-python = ">=3.13"
11
+ dependencies = [
12
+ "hexastack-core",
13
+ "hexastack-cqrs",
14
+ "pydantic>=2.10.0",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ fastapi = [
19
+ "fastapi>=0.141.1",
20
+ ]
21
+ nicegui = [
22
+ "fastapi>=0.141.1",
23
+ "nicegui>=2.0.0",
24
+ ]
25
+ devtools = [
26
+ "hexastack-ui[nicegui]",
27
+ ]
28
+ textual = [
29
+ "textual>=0.70.0",
30
+ ]
31
+ all = [
32
+ "hexastack-ui[nicegui,textual]",
33
+ ]
34
+
35
+ [build-system]
36
+ requires = ["uv_build>=0.12.3,<0.13.0"]
37
+ build-backend = "uv_build"
38
+
39
+ [tool.uv.sources]
40
+ hexastack-core = { workspace = true }
41
+ hexastack-cqrs = { workspace = true }
42
+
43
+ [tool.importlinter]
44
+ root_packages = ["hexastack_ui"]
45
+
46
+ [[tool.importlinter.contracts]]
47
+ name = "Hexagonal architecture layer hierarchy"
48
+ type = "layers"
49
+ containers = ["hexastack_ui"]
50
+ layers = [
51
+ "infra",
52
+ "adapters",
53
+ "ports",
54
+ "domain",
55
+ ]
@@ -0,0 +1,35 @@
1
+ """Hexastack UI - Reactive UI Presentation Adapters and DevTools Dashboard."""
2
+
3
+ from hexastack_ui.adapters import (
4
+ check_nicegui_installed,
5
+ dispatch_command,
6
+ dispatch_query,
7
+ mount_devtools_dashboard,
8
+ mount_ui_app,
9
+ ui_page,
10
+ )
11
+ from hexastack_ui.domain import (
12
+ CQRSMessageSummary,
13
+ DevToolsDashboardState,
14
+ FeatureFlagSummary,
15
+ ServiceBindingSummary,
16
+ )
17
+ from hexastack_ui.ports import (
18
+ DevToolsPresenterPort,
19
+ UIPresenterPort,
20
+ )
21
+
22
+ __all__ = [
23
+ "check_nicegui_installed",
24
+ "CQRSMessageSummary",
25
+ "DevToolsDashboardState",
26
+ "DevToolsPresenterPort",
27
+ "dispatch_command",
28
+ "dispatch_query",
29
+ "FeatureFlagSummary",
30
+ "mount_devtools_dashboard",
31
+ "mount_ui_app",
32
+ "ServiceBindingSummary",
33
+ "ui_page",
34
+ "UIPresenterPort",
35
+ ]
@@ -0,0 +1,19 @@
1
+ """UI presentation adapters for Hexastack."""
2
+
3
+ from hexastack_ui.adapters.nicegui import (
4
+ check_nicegui_installed,
5
+ dispatch_command,
6
+ dispatch_query,
7
+ mount_devtools_dashboard,
8
+ mount_ui_app,
9
+ ui_page,
10
+ )
11
+
12
+ __all__ = [
13
+ "check_nicegui_installed",
14
+ "dispatch_command",
15
+ "dispatch_query",
16
+ "mount_devtools_dashboard",
17
+ "mount_ui_app",
18
+ "ui_page",
19
+ ]
@@ -0,0 +1,21 @@
1
+ """NiceGUI reactive presentation adapter and DevTools dashboard."""
2
+
3
+ from hexastack_ui.adapters.nicegui.devtools import mount_devtools_dashboard
4
+ from hexastack_ui.adapters.nicegui.dispatch import (
5
+ dispatch_command,
6
+ dispatch_query,
7
+ )
8
+ from hexastack_ui.adapters.nicegui.page import (
9
+ check_nicegui_installed,
10
+ mount_ui_app,
11
+ ui_page,
12
+ )
13
+
14
+ __all__ = [
15
+ "check_nicegui_installed",
16
+ "dispatch_command",
17
+ "dispatch_query",
18
+ "mount_devtools_dashboard",
19
+ "mount_ui_app",
20
+ "ui_page",
21
+ ]
@@ -0,0 +1,384 @@
1
+ """Turnkey DevTools diagnostic dashboard built on NiceGUI.
2
+
3
+ Notes/Architectural Intent:
4
+ Renders an interactive inspector for CQRS registries, command bus middlewares,
5
+ feature flag state, and DI container service bindings.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from rodi import Container
14
+
15
+ from hexastack_cqrs.infra.pipeline import ExecutionPipeline
16
+ from hexastack_ui.adapters.nicegui.page import check_nicegui_installed, mount_ui_app
17
+
18
+ if TYPE_CHECKING:
19
+ from fastapi import FastAPI
20
+
21
+ __all__ = [
22
+ "mount_devtools_dashboard",
23
+ ]
24
+
25
+ _STATIC_DIR = Path(__file__).parent / "static"
26
+
27
+
28
+ def _render_cqrs_messages(container: Container) -> None:
29
+ """Render table of registered CQRS message contracts."""
30
+ from nicegui import ui
31
+
32
+ from hexastack_cqrs.infra.registries.command import CommandRegistry
33
+ from hexastack_cqrs.infra.registries.query import QueryRegistry
34
+
35
+ ui.label("Registered CQRS Commands & Queries").classes("hx-section-heading")
36
+
37
+ cmd_reg = (
38
+ container.resolve(CommandRegistry) if CommandRegistry in container else None
39
+ )
40
+ qry_reg = container.resolve(QueryRegistry) if QueryRegistry in container else None
41
+
42
+ commands_list = (
43
+ [
44
+ {"type": "Command", "name": getattr(k, "__name__", str(k))}
45
+ for k in cmd_reg.all.values()
46
+ ]
47
+ if cmd_reg
48
+ else []
49
+ )
50
+ queries_list = (
51
+ [
52
+ {"type": "Query", "name": getattr(k, "__name__", str(k))}
53
+ for k in qry_reg.all.values()
54
+ ]
55
+ if qry_reg
56
+ else []
57
+ )
58
+ cqrs_rows = commands_list + queries_list
59
+
60
+ if cqrs_rows:
61
+ ui.table(
62
+ columns=[
63
+ {
64
+ "name": "type",
65
+ "label": "Message Type",
66
+ "field": "type",
67
+ "sortable": True,
68
+ },
69
+ {
70
+ "name": "name",
71
+ "label": "Class Name",
72
+ "field": "name",
73
+ "sortable": True,
74
+ },
75
+ ],
76
+ rows=cqrs_rows,
77
+ row_key="name",
78
+ ).classes("w-full mb-6")
79
+ else:
80
+ ui.label("No CQRS messages registered in container.").classes("hx-empty-label")
81
+
82
+
83
+ def _render_middleware_chain(middlewares: list[Any]) -> None:
84
+ """Render visual chain of active command bus middlewares."""
85
+ from nicegui import ui
86
+
87
+ ui.label("Command Bus Middleware Pipeline").classes("hx-section-heading")
88
+ if middlewares:
89
+ with (
90
+ ui.card().classes("hx-card"),
91
+ ui.row().classes("items-center flex-wrap gap-2"),
92
+ ):
93
+ for idx, mw in enumerate(middlewares, 1):
94
+ mw_name = type(mw).__name__
95
+ ui.chip(
96
+ f"{idx}. {mw_name}",
97
+ icon="security" if "Auth" in mw_name else "filter_alt",
98
+ color="blue-9",
99
+ text_color="white",
100
+ ).classes("font-medium")
101
+ if idx < len(middlewares):
102
+ ui.icon("arrow_forward", size="sm")
103
+ ui.icon("arrow_forward", size="sm")
104
+ ui.chip(
105
+ "Handler Execution",
106
+ icon="play_circle",
107
+ color="green-9",
108
+ text_color="white",
109
+ ).classes("font-bold")
110
+ else:
111
+ ui.label("No active middlewares attached to CommandBus.").classes(
112
+ "hx-empty-label"
113
+ )
114
+
115
+
116
+ async def _dispatch_ping(
117
+ ping_value: str,
118
+ container: Container,
119
+ pipeline: ExecutionPipeline | None,
120
+ middlewares: list[Any],
121
+ log_output: Any,
122
+ ) -> None:
123
+ """Execute ping dispatch across pipeline and log output."""
124
+ log_output.push(f"➡️ [DISPATCH] PingDemoCommand(message='{ping_value}')")
125
+ for mw in middlewares:
126
+ log_output.push(
127
+ f" ↳ [MIDDLEWARE] Intercepting through {type(mw).__name__}..."
128
+ )
129
+
130
+ try:
131
+ active_pipeline = pipeline
132
+ if active_pipeline is None and ExecutionPipeline in container:
133
+ active_pipeline = container.resolve(ExecutionPipeline)
134
+
135
+ if active_pipeline is not None:
136
+ cmd_cls = None
137
+ from hexastack_cqrs.infra.registries.command import (
138
+ CommandRegistry,
139
+ )
140
+
141
+ if CommandRegistry in container:
142
+ creg = container.resolve(CommandRegistry)
143
+ for name, cls in creg.all.items():
144
+ if "ping" in name.lower():
145
+ cmd_cls = cls
146
+ break
147
+
148
+ if cmd_cls is not None:
149
+ cmd_instance = cmd_cls.model_validate({"message": ping_value})
150
+ res = active_pipeline.execute(cmd_instance)
151
+ log_output.push(f"✅ [SUCCESS] Result: {res}")
152
+ else:
153
+ res = active_pipeline.execute_by_name(
154
+ "PingDemoCommand", {"message": ping_value}
155
+ )
156
+ log_output.push(f"✅ [SUCCESS] Result: {res}")
157
+ else:
158
+ log_output.push(
159
+ "⚠️ [WARNING] ExecutionPipeline instance not directly bound."
160
+ )
161
+ except Exception as exc:
162
+ log_output.push(f"❌ [ERROR] Execution failed: {exc}")
163
+
164
+
165
+ def _render_live_runner(
166
+ container: Container,
167
+ pipeline: ExecutionPipeline | None,
168
+ middlewares: list[Any],
169
+ ) -> None:
170
+ """Render interactive test runner card for ping command dispatch."""
171
+ from nicegui import ui
172
+
173
+ ui.label("Interactive Pipeline Execution").classes("hx-section-heading")
174
+ with ui.card().classes("hx-card"):
175
+ ui.label("Dispatch PingDemoCommand across the middleware pipeline:").classes(
176
+ "hx-subtext"
177
+ )
178
+ with ui.row().classes("items-center gap-3 w-full"):
179
+ ping_input = ui.input(
180
+ label="Message Payload", value="Hello from Hexastack DevTools!"
181
+ ).classes("flex-grow")
182
+ log_output = ui.log(max_lines=10).classes("hx-log-console")
183
+
184
+ async def _run_ping() -> None:
185
+ await _dispatch_ping(
186
+ str(ping_input.value),
187
+ container,
188
+ pipeline,
189
+ middlewares,
190
+ log_output,
191
+ )
192
+
193
+ ui.button(
194
+ "Dispatch Ping Command", on_click=_run_ping, icon="send", color="blue-9"
195
+ ).classes("text-white font-medium shadow-sm")
196
+
197
+
198
+ def _render_cqrs_tab(
199
+ container: Container, pipeline: ExecutionPipeline | None = None
200
+ ) -> None:
201
+ """Render the CQRS tab panel showing registered messages, middleware pipeline, and live runner."""
202
+ from hexastack_cqrs.ports.buses import CommandBusPort
203
+
204
+ _render_cqrs_messages(container)
205
+ cmd_bus = container.resolve(CommandBusPort) if CommandBusPort in container else None
206
+ middlewares = getattr(cmd_bus, "_middleware", []) if cmd_bus else []
207
+ _render_middleware_chain(middlewares)
208
+ _render_live_runner(container, pipeline, middlewares)
209
+
210
+
211
+ def _render_flags_tab(container: Container) -> None:
212
+ """Render the feature flags tab panel showing active flags."""
213
+ from nicegui import ui
214
+
215
+ from hexastack_core.ports.feature_flags import FeatureFlagPort
216
+
217
+ ui.label("Active Feature Flags").classes("hx-section-heading")
218
+ flags_adapter = (
219
+ container.resolve(FeatureFlagPort) if FeatureFlagPort in container else None
220
+ )
221
+
222
+ get_all_fn = getattr(flags_adapter, "get_all_flags", None)
223
+ if callable(get_all_fn):
224
+ flags_data: dict[str, Any] = get_all_fn()
225
+ if flags_data:
226
+ flag_rows = [{"flag": k, "enabled": str(v)} for k, v in flags_data.items()]
227
+ ui.table(
228
+ columns=[
229
+ {
230
+ "name": "flag",
231
+ "label": "Flag Key",
232
+ "field": "flag",
233
+ "sortable": True,
234
+ },
235
+ {
236
+ "name": "enabled",
237
+ "label": "Status",
238
+ "field": "enabled",
239
+ "sortable": True,
240
+ },
241
+ ],
242
+ rows=flag_rows,
243
+ row_key="flag",
244
+ pagination={"rowsPerPage": 10},
245
+ ).classes("w-full")
246
+ else:
247
+ ui.label("No feature flags currently configured in provider.").classes(
248
+ "hx-empty-label"
249
+ )
250
+ else:
251
+ ui.label(
252
+ "Feature flag provider not available or does not support listing."
253
+ ).classes("hx-empty-label")
254
+
255
+
256
+ def _render_container_tab(container: Container) -> None:
257
+ """Render the DI container tab panel showing registered services."""
258
+ from nicegui import ui
259
+
260
+ ui.label("Dependency Injection Services").classes("hx-section-heading")
261
+
262
+ service_map = getattr(container, "_map", {})
263
+ services: list[dict[str, str]] = []
264
+
265
+ for cls, resolver in service_map.items():
266
+ service_name = getattr(cls, "__qualname__", getattr(cls, "__name__", str(cls)))
267
+ module_name = getattr(cls, "__module__", "")
268
+ resolver_desc = str(resolver).strip("<>")
269
+
270
+ services.append(
271
+ {
272
+ "service": service_name,
273
+ "module": module_name,
274
+ "resolver": resolver_desc,
275
+ }
276
+ )
277
+
278
+ services.sort(key=lambda s: s["service"])
279
+
280
+ if services:
281
+ ui.table(
282
+ columns=[
283
+ {
284
+ "name": "service",
285
+ "label": "Registered Port / Service",
286
+ "field": "service",
287
+ "sortable": True,
288
+ },
289
+ {
290
+ "name": "module",
291
+ "label": "Module",
292
+ "field": "module",
293
+ "sortable": True,
294
+ },
295
+ {
296
+ "name": "resolver",
297
+ "label": "Binding / Lifetime",
298
+ "field": "resolver",
299
+ "sortable": True,
300
+ },
301
+ ],
302
+ rows=services,
303
+ row_key="service",
304
+ pagination={"rowsPerPage": 10},
305
+ ).classes("w-full")
306
+ else:
307
+ ui.label("No direct services found in container introspection.").classes(
308
+ "hx-empty-label"
309
+ )
310
+
311
+
312
+ def _render_devtools_content(
313
+ container: Container,
314
+ pipeline: ExecutionPipeline | None,
315
+ title: str,
316
+ ) -> None:
317
+ """Render the full DevTools dashboard UI components."""
318
+ from nicegui import ui
319
+
320
+ ui.add_head_html(
321
+ '<link rel="stylesheet" type="text/css" href="/_hexastack/ui/static/devtools.css">'
322
+ )
323
+
324
+ with ui.header().classes("hx-header"):
325
+ with ui.row().classes("hx-header-title"):
326
+ ui.icon("layers", size="md").classes("text-blue-400")
327
+ ui.label(title).classes("hx-header-text")
328
+ ui.badge("v0.3.0", color="blue-10").classes("text-xs font-semibold text-white")
329
+
330
+ with ui.tabs().classes("w-full bg-slate-100 dark:bg-slate-800") as tabs:
331
+ tab_cqrs = ui.tab("CQRS Registry", icon="bolt")
332
+ tab_flags = ui.tab("Feature Flags", icon="toggle_on")
333
+ tab_container = ui.tab("DI Container", icon="hub")
334
+
335
+ with ui.tab_panels(tabs, value=tab_cqrs).classes("w-full p-6"):
336
+ with ui.tab_panel(tab_cqrs):
337
+ _render_cqrs_tab(container, pipeline=pipeline)
338
+
339
+ with ui.tab_panel(tab_flags):
340
+ _render_flags_tab(container)
341
+
342
+ with ui.tab_panel(tab_container):
343
+ _render_container_tab(container)
344
+
345
+
346
+ def mount_devtools_dashboard(
347
+ app: FastAPI,
348
+ container: Container,
349
+ pipeline: ExecutionPipeline | None = None,
350
+ *,
351
+ path: str = "/_devtools",
352
+ title: str = "Hexastack DevTools",
353
+ ) -> None:
354
+ """Mount the default Hexastack interactive DevTools dashboard on FastAPI.
355
+
356
+ Args:
357
+ app: Target FastAPI application.
358
+ container: Application rodi.Container instance.
359
+ pipeline: Optional ExecutionPipeline instance.
360
+ path: URL path where the dashboard is mounted (default '/_devtools').
361
+ title: Dashboard page title.
362
+
363
+ Returns:
364
+ None.
365
+
366
+ Raises:
367
+ MissingDependencyError: If NiceGUI is not installed.
368
+
369
+ Notes/Architectural Intent:
370
+ Renders a rich developer console inspecting CQRS buses, feature flags,
371
+ and DI container services built entirely using the NiceGUI primitives.
372
+ """
373
+ check_nicegui_installed()
374
+ from nicegui import app as nicegui_app
375
+ from nicegui import ui
376
+
377
+ if _STATIC_DIR.is_dir():
378
+ nicegui_app.add_static_files("/_hexastack/ui/static", str(_STATIC_DIR))
379
+
380
+ mount_ui_app(app, title=title)
381
+
382
+ @ui.page(path, title=title)
383
+ def devtools_page():
384
+ _render_devtools_content(container, pipeline, title)
@@ -0,0 +1,66 @@
1
+ """CQRS dispatch helpers for NiceGUI reactive event handlers.
2
+
3
+ Notes/Architectural Intent:
4
+ Allows NiceGUI buttons, form submissions, and reactive event loops to invoke
5
+ ExecutionPipeline commands and queries synchronously or asynchronously.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import inspect
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ if TYPE_CHECKING:
14
+ from hexastack_core.domain import Command, Query
15
+ from hexastack_cqrs.infra.pipeline import ExecutionPipeline
16
+
17
+ __all__ = [
18
+ "dispatch_command",
19
+ "dispatch_query",
20
+ ]
21
+
22
+
23
+ async def dispatch_command(pipeline: ExecutionPipeline, command: Command) -> Any:
24
+ """Dispatch a CQRS command from a NiceGUI interactive event handler.
25
+
26
+ Args:
27
+ pipeline: ExecutionPipeline instance.
28
+ command: Command instance to execute.
29
+
30
+ Returns:
31
+ Result returned by command execution.
32
+
33
+ Raises:
34
+ Exception: Propagates any handler or middleware execution errors.
35
+
36
+ Notes/Architectural Intent:
37
+ Executes a command through the standard ExecutionPipeline,
38
+ transparently awaiting async handler returns or returning synchronous values.
39
+ """
40
+ result = pipeline.execute(command)
41
+ if inspect.isawaitable(result):
42
+ return await result
43
+ return result
44
+
45
+
46
+ async def dispatch_query(pipeline: ExecutionPipeline, query: Query) -> Any:
47
+ """Dispatch a CQRS query from a NiceGUI interactive event handler or data loader.
48
+
49
+ Args:
50
+ pipeline: ExecutionPipeline instance.
51
+ query: Query instance to execute.
52
+
53
+ Returns:
54
+ Result returned by query execution.
55
+
56
+ Raises:
57
+ Exception: Propagates any query handler execution errors.
58
+
59
+ Notes/Architectural Intent:
60
+ Executes a query through the standard ExecutionPipeline,
61
+ transparently awaiting async handler returns or returning synchronous values.
62
+ """
63
+ result = pipeline.execute(query)
64
+ if inspect.isawaitable(result):
65
+ return await result
66
+ return result
@@ -0,0 +1,116 @@
1
+ """NiceGUI reactive page and mounting primitives.
2
+
3
+ Notes/Architectural Intent:
4
+ Provides decorators and mounting functions for integrating NiceGUI with FastAPI/Starlette.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Callable
10
+ from typing import Any
11
+
12
+ from hexastack_core.domain.exceptions import MissingDependencyError
13
+
14
+ __all__ = [
15
+ "check_nicegui_installed",
16
+ "mount_ui_app",
17
+ "ui_page",
18
+ ]
19
+
20
+
21
+ def check_nicegui_installed() -> None:
22
+ """Verify NiceGUI is available in runtime environment.
23
+
24
+ Raises:
25
+ MissingDependencyError: If NiceGUI is not installed.
26
+
27
+ Notes/Architectural Intent:
28
+ Enables lazy import error reporting with installation hints.
29
+ """
30
+ try:
31
+ import nicegui # noqa: F401
32
+ except ImportError as e:
33
+ raise MissingDependencyError(
34
+ "NiceGUI is required for hexastack-ui NiceGUI support. "
35
+ "Install with 'pip install hexastack-ui[nicegui]' or 'pip install nicegui'."
36
+ ) from e
37
+
38
+
39
+ def ui_page(
40
+ path: str,
41
+ *,
42
+ title: str | None = None,
43
+ viewport: str | None = None,
44
+ favicon: str | None = None,
45
+ dark: bool | None = None,
46
+ response_timeout: float = 3.0,
47
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
48
+ """Declarative decorator registering a NiceGUI reactive UI page.
49
+
50
+ Args:
51
+ path: URL path for the page (e.g. '/dashboard', '/users').
52
+ title: Optional browser tab title.
53
+ viewport: Optional viewport meta tag string.
54
+ favicon: Optional favicon path or URL.
55
+ dark: Optional dark mode setting (True/False/None).
56
+ response_timeout: Maximum seconds to wait for client connection response.
57
+
58
+ Returns:
59
+ Decorated page function.
60
+
61
+ Raises:
62
+ MissingDependencyError: If NiceGUI is not installed.
63
+
64
+ Notes/Architectural Intent:
65
+ Wraps `nicegui.ui.page` while cleanly decoupling application modules from
66
+ direct NiceGUI hard dependency at module import time.
67
+ """
68
+ check_nicegui_installed()
69
+ from nicegui import ui
70
+
71
+ return ui.page(
72
+ path,
73
+ title=title,
74
+ viewport=viewport,
75
+ favicon=favicon,
76
+ dark=dark,
77
+ response_timeout=response_timeout,
78
+ )
79
+
80
+
81
+ def mount_ui_app(
82
+ app: Any,
83
+ *,
84
+ title: str = "Hexastack UI",
85
+ viewport: str = "width=device-width, initial-scale=1",
86
+ favicon: str | None = None,
87
+ dark: bool | None = None,
88
+ ) -> None:
89
+ """Mount NiceGUI reactive engine onto an existing FastAPI/Starlette application instance.
90
+
91
+ Args:
92
+ app: Target FastAPI or Starlette application.
93
+ title: Application default page title.
94
+ viewport: Viewport meta tag.
95
+ favicon: Optional favicon.
96
+ dark: Dark mode preference.
97
+
98
+ Returns:
99
+ None.
100
+
101
+ Raises:
102
+ MissingDependencyError: If NiceGUI is not installed.
103
+
104
+ Notes/Architectural Intent:
105
+ Integrates NiceGUI's Socket.IO and static assets into the host server lifecycle.
106
+ """
107
+ check_nicegui_installed()
108
+ from nicegui import ui
109
+
110
+ ui.run_with(
111
+ app,
112
+ title=title,
113
+ viewport=viewport,
114
+ favicon=favicon,
115
+ dark=dark,
116
+ )
@@ -0,0 +1,85 @@
1
+ /* Hexastack DevTools Dashboard Stylesheet */
2
+
3
+ :root {
4
+ --hx-primary: #1e40af;
5
+ --hx-secondary: #065f46;
6
+ --hx-accent: #5b21b6;
7
+ --hx-dark-bg: #0f172a;
8
+ --hx-card-bg: #f8fafc;
9
+ --hx-card-dark-bg: #0f172a;
10
+ --hx-log-bg: #020617;
11
+ --hx-log-text: #34d399;
12
+ }
13
+
14
+ .hx-header {
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ background-color: var(--hx-dark-bg);
19
+ color: #ffffff;
20
+ padding: 0.75rem 1.5rem;
21
+ }
22
+
23
+ .hx-header-title {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: 0.75rem;
27
+ }
28
+
29
+ .hx-header-text {
30
+ font-size: 1.25rem;
31
+ font-weight: 700;
32
+ letter-spacing: -0.025em;
33
+ }
34
+
35
+ .hx-card {
36
+ width: 100%;
37
+ background-color: var(--hx-card-bg);
38
+ border: 1px solid #e2e8f0;
39
+ padding: 1rem;
40
+ margin-bottom: 1.5rem;
41
+ border-radius: 0.5rem;
42
+ }
43
+
44
+ .body--dark .hx-card {
45
+ background-color: var(--hx-card-dark-bg);
46
+ border-color: #334155;
47
+ }
48
+
49
+ .hx-log-console {
50
+ width: 100%;
51
+ height: 8rem;
52
+ background-color: var(--hx-log-bg);
53
+ color: var(--hx-log-text);
54
+ padding: 0.5rem;
55
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
56
+ font-size: 0.75rem;
57
+ border-radius: 0.375rem;
58
+ border: 1px solid #1e293b;
59
+ }
60
+
61
+ .hx-section-heading {
62
+ font-size: 1.125rem;
63
+ font-weight: 600;
64
+ margin-bottom: 1rem;
65
+ }
66
+
67
+ .hx-subtext {
68
+ font-size: 0.875rem;
69
+ color: #64748b;
70
+ margin-bottom: 0.75rem;
71
+ }
72
+
73
+ .body--dark .hx-subtext {
74
+ color: #94a3b8;
75
+ }
76
+
77
+ .hx-empty-label {
78
+ color: #64748b;
79
+ font-style: italic;
80
+ margin-bottom: 1.5rem;
81
+ }
82
+
83
+ .body--dark .hx-empty-label {
84
+ color: #94a3b8;
85
+ }
@@ -0,0 +1,15 @@
1
+ """Domain entities and value objects for Hexastack UI."""
2
+
3
+ from hexastack_ui.domain.models import (
4
+ CQRSMessageSummary,
5
+ DevToolsDashboardState,
6
+ FeatureFlagSummary,
7
+ ServiceBindingSummary,
8
+ )
9
+
10
+ __all__ = [
11
+ "CQRSMessageSummary",
12
+ "DevToolsDashboardState",
13
+ "FeatureFlagSummary",
14
+ "ServiceBindingSummary",
15
+ ]
@@ -0,0 +1,95 @@
1
+ """Domain models for UI presentations and DevTools state.
2
+
3
+ Notes/Architectural Intent:
4
+ Pure domain representations of diagnostic records, CQRS messages,
5
+ service bindings, and UI dashboard state independent of any UI framework.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field
11
+
12
+ __all__ = [
13
+ "CQRSMessageSummary",
14
+ "DevToolsDashboardState",
15
+ "FeatureFlagSummary",
16
+ "ServiceBindingSummary",
17
+ ]
18
+
19
+
20
+ class CQRSMessageSummary(BaseModel):
21
+ """Summary of a registered CQRS Command or Query contract.
22
+
23
+ Notes/Architectural Intent:
24
+ Represents message metadata for visual exploration across developer dashboards.
25
+ """
26
+
27
+ model_config = ConfigDict(frozen=True)
28
+
29
+ name: str = Field(
30
+ description="Canonical class or registration name of the CQRS message"
31
+ )
32
+ message_type: str = Field(description="Type of message ('Command' or 'Query')")
33
+ module: str = Field(
34
+ default="", description="Originating module of the message definition"
35
+ )
36
+
37
+
38
+ class FeatureFlagSummary(BaseModel):
39
+ """Summary of an active feature flag and its current state.
40
+
41
+ Notes/Architectural Intent:
42
+ Encapsulates feature flag key/value status for dashboard toggling and inspection.
43
+ """
44
+
45
+ model_config = ConfigDict(frozen=True)
46
+
47
+ key: str = Field(description="Unique identifier key of the feature flag")
48
+ enabled: bool = Field(description="Boolean enabled status of the flag")
49
+ description: str = Field(
50
+ default="", description="Optional human-readable description"
51
+ )
52
+
53
+
54
+ class ServiceBindingSummary(BaseModel):
55
+ """Summary of a dependency injection service registration.
56
+
57
+ Notes/Architectural Intent:
58
+ Represents service lifetime, interface, and concrete implementation binding metadata.
59
+ """
60
+
61
+ model_config = ConfigDict(frozen=True)
62
+
63
+ service: str = Field(description="Service interface or class name")
64
+ module: str = Field(
65
+ default="", description="Module path where the service is defined"
66
+ )
67
+ resolver: str = Field(
68
+ default="", description="Binding description or lifetime resolution strategy"
69
+ )
70
+
71
+
72
+ class DevToolsDashboardState(BaseModel):
73
+ """Aggregated snapshot of Hexastack runtime state for DevTools inspection.
74
+
75
+ Notes/Architectural Intent:
76
+ Immutable aggregate containing all diagnostic summaries for UI rendering.
77
+ """
78
+
79
+ model_config = ConfigDict(frozen=True)
80
+
81
+ commands: list[CQRSMessageSummary] = Field(
82
+ default_factory=list, description="Registered commands"
83
+ )
84
+ queries: list[CQRSMessageSummary] = Field(
85
+ default_factory=list, description="Registered queries"
86
+ )
87
+ flags: list[FeatureFlagSummary] = Field(
88
+ default_factory=list, description="Active feature flags"
89
+ )
90
+ services: list[ServiceBindingSummary] = Field(
91
+ default_factory=list, description="Registered DI services"
92
+ )
93
+ middlewares: list[str] = Field(
94
+ default_factory=list, description="Active middleware pipeline names"
95
+ )
@@ -0,0 +1,7 @@
1
+ """Infrastructure layer for Hexastack UI."""
2
+
3
+ from hexastack_ui.infra.bootstrap import UIBootstrapper
4
+
5
+ __all__ = [
6
+ "UIBootstrapper",
7
+ ]
@@ -0,0 +1,35 @@
1
+ """Infrastructure bootstrapping and DI configuration for UI adapters.
2
+
3
+ Notes/Architectural Intent:
4
+ Provides automatic registration of UI presentation adapters into rodi.Container.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from rodi import Container
12
+
13
+ __all__ = [
14
+ "UIBootstrapper",
15
+ ]
16
+
17
+
18
+ class UIBootstrapper:
19
+ """Bootstrapper for Hexastack UI presentation components."""
20
+
21
+ @classmethod
22
+ def bootstrap(cls, container: Container, **kwargs: Any) -> None:
23
+ """Register UI adapters and ports into the DI container.
24
+
25
+ Args:
26
+ container: Application rodi.Container instance.
27
+ **kwargs: Extra bootstrap arguments.
28
+
29
+ Returns:
30
+ None.
31
+
32
+ Notes/Architectural Intent:
33
+ Ensures UI presenters and state providers can be resolved cleanly across services.
34
+ """
35
+ # Future presenter registrations can be bound here
@@ -0,0 +1,11 @@
1
+ """Ports and interface contracts for Hexastack UI."""
2
+
3
+ from hexastack_ui.ports.presenter import (
4
+ DevToolsPresenterPort,
5
+ UIPresenterPort,
6
+ )
7
+
8
+ __all__ = [
9
+ "DevToolsPresenterPort",
10
+ "UIPresenterPort",
11
+ ]
@@ -0,0 +1,65 @@
1
+ """Abstract presentation ports for Hexastack UI integrations.
2
+
3
+ Notes/Architectural Intent:
4
+ Defines UI framework-agnostic interfaces for rendering developer consoles,
5
+ mounting web/TUI application lifecycles, and collecting diagnostic state.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from typing import Any
12
+
13
+ from hexastack_ui.domain.models import DevToolsDashboardState
14
+
15
+ __all__ = [
16
+ "DevToolsPresenterPort",
17
+ "UIPresenterPort",
18
+ ]
19
+
20
+
21
+ class UIPresenterPort(ABC):
22
+ """Abstract port for mounting and lifecycle management of UI presentation engines."""
23
+
24
+ @abstractmethod
25
+ def mount(self, target_app: Any, **kwargs: Any) -> None:
26
+ """Mount UI presentation engine onto a host server or execution runtime.
27
+
28
+ Args:
29
+ target_app: Target application or server instance.
30
+ **kwargs: Additional framework-specific configuration options.
31
+
32
+ Returns:
33
+ None.
34
+
35
+ Raises:
36
+ Exception: If mounting fails.
37
+
38
+ Notes/Architectural Intent:
39
+ Enables generic mounting logic across HTTP servers (FastAPI/Starlette) or TUI runtimes.
40
+ """
41
+
42
+
43
+ class DevToolsPresenterPort(ABC):
44
+ """Abstract port for rendering interactive DevTools diagnostic dashboards."""
45
+
46
+ @abstractmethod
47
+ def render_dashboard(
48
+ self, state: DevToolsDashboardState, target_app: Any, **kwargs: Any
49
+ ) -> None:
50
+ """Render diagnostic dashboard given a snapshot of the runtime state.
51
+
52
+ Args:
53
+ state: Current aggregated runtime state.
54
+ target_app: Host application instance.
55
+ **kwargs: Additional presentation parameters.
56
+
57
+ Returns:
58
+ None.
59
+
60
+ Raises:
61
+ Exception: If dashboard rendering fails.
62
+
63
+ Notes/Architectural Intent:
64
+ Decouples dashboard visualization (NiceGUI, Textual) from state extraction.
65
+ """