vmx 2.6.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.
- vmx-2.6.0/.gitignore +120 -0
- vmx-2.6.0/CHANGELOG.md +392 -0
- vmx-2.6.0/PKG-INFO +271 -0
- vmx-2.6.0/README.md +232 -0
- vmx-2.6.0/RELEASING.md +133 -0
- vmx-2.6.0/pyproject.toml +98 -0
- vmx-2.6.0/scripts/smoke_test.py +65 -0
- vmx-2.6.0/src/vmx/__about__.py +10 -0
- vmx-2.6.0/src/vmx/__init__.py +289 -0
- vmx-2.6.0/src/vmx/aggregates/__init__.py +60 -0
- vmx-2.6.0/src/vmx/aggregates/aggregate_vm.py +653 -0
- vmx-2.6.0/src/vmx/aggregates/builders.py +451 -0
- vmx-2.6.0/src/vmx/builders/__init__.py +14 -0
- vmx-2.6.0/src/vmx/builders/_validation.py +31 -0
- vmx-2.6.0/src/vmx/builders/exceptions.py +20 -0
- vmx-2.6.0/src/vmx/capabilities/__init__.py +56 -0
- vmx-2.6.0/src/vmx/capabilities/crud.py +40 -0
- vmx-2.6.0/src/vmx/capabilities/current_crud.py +21 -0
- vmx-2.6.0/src/vmx/capabilities/dialog.py +29 -0
- vmx-2.6.0/src/vmx/capabilities/expandable_state.py +68 -0
- vmx-2.6.0/src/vmx/capabilities/expansion.py +33 -0
- vmx-2.6.0/src/vmx/capabilities/filter.py +28 -0
- vmx-2.6.0/src/vmx/capabilities/lifecycle_capabilities.py +32 -0
- vmx-2.6.0/src/vmx/capabilities/management.py +16 -0
- vmx-2.6.0/src/vmx/capabilities/pageable.py +103 -0
- vmx-2.6.0/src/vmx/capabilities/search.py +21 -0
- vmx-2.6.0/src/vmx/capabilities/searchable_state.py +103 -0
- vmx-2.6.0/src/vmx/capabilities/selection.py +29 -0
- vmx-2.6.0/src/vmx/collections/__init__.py +34 -0
- vmx-2.6.0/src/vmx/collections/batch.py +45 -0
- vmx-2.6.0/src/vmx/collections/collection_changed.py +21 -0
- vmx-2.6.0/src/vmx/collections/observable_dictionary.py +288 -0
- vmx-2.6.0/src/vmx/collections/observable_list.py +225 -0
- vmx-2.6.0/src/vmx/collections/paged_composition.py +240 -0
- vmx-2.6.0/src/vmx/collections/serviced_observable_collection.py +136 -0
- vmx-2.6.0/src/vmx/commands/__init__.py +48 -0
- vmx-2.6.0/src/vmx/commands/composite_command.py +55 -0
- vmx-2.6.0/src/vmx/commands/confirmation_decorator_command.py +62 -0
- vmx-2.6.0/src/vmx/commands/decorator_command.py +67 -0
- vmx-2.6.0/src/vmx/commands/fluent.py +109 -0
- vmx-2.6.0/src/vmx/commands/modeled_crud_commands.py +88 -0
- vmx-2.6.0/src/vmx/commands/protocols.py +57 -0
- vmx-2.6.0/src/vmx/commands/relay_command.py +278 -0
- vmx-2.6.0/src/vmx/components/__init__.py +44 -0
- vmx-2.6.0/src/vmx/components/base.py +457 -0
- vmx-2.6.0/src/vmx/components/builders.py +346 -0
- vmx-2.6.0/src/vmx/components/component_vm.py +156 -0
- vmx-2.6.0/src/vmx/components/protocols.py +136 -0
- vmx-2.6.0/src/vmx/components/readonly_component_vm.py +77 -0
- vmx-2.6.0/src/vmx/composites/__init__.py +29 -0
- vmx-2.6.0/src/vmx/composites/builders.py +265 -0
- vmx-2.6.0/src/vmx/composites/composite_vm.py +517 -0
- vmx-2.6.0/src/vmx/composites/protocols.py +58 -0
- vmx-2.6.0/src/vmx/dialogs/__init__.py +18 -0
- vmx-2.6.0/src/vmx/dialogs/dialog_service.py +96 -0
- vmx-2.6.0/src/vmx/dialogs/null_dialog_service.py +56 -0
- vmx-2.6.0/src/vmx/forms/__init__.py +15 -0
- vmx-2.6.0/src/vmx/forms/builders.py +107 -0
- vmx-2.6.0/src/vmx/forms/form_vm.py +229 -0
- vmx-2.6.0/src/vmx/forwarding/__init__.py +14 -0
- vmx-2.6.0/src/vmx/forwarding/component.py +153 -0
- vmx-2.6.0/src/vmx/forwarding/composite.py +214 -0
- vmx-2.6.0/src/vmx/groups/__init__.py +20 -0
- vmx-2.6.0/src/vmx/groups/builders.py +118 -0
- vmx-2.6.0/src/vmx/groups/group_vm.py +305 -0
- vmx-2.6.0/src/vmx/hierarchical/__init__.py +11 -0
- vmx-2.6.0/src/vmx/hierarchical/builders.py +157 -0
- vmx-2.6.0/src/vmx/hierarchical/hierarchical_vm.py +298 -0
- vmx-2.6.0/src/vmx/lifecycle/__init__.py +24 -0
- vmx-2.6.0/src/vmx/lifecycle/_data/__init__.py +1 -0
- vmx-2.6.0/src/vmx/lifecycle/exceptions.py +27 -0
- vmx-2.6.0/src/vmx/lifecycle/status.py +31 -0
- vmx-2.6.0/src/vmx/lifecycle/transition_validator.py +116 -0
- vmx-2.6.0/src/vmx/localization/__init__.py +12 -0
- vmx-2.6.0/src/vmx/localization/localizer.py +15 -0
- vmx-2.6.0/src/vmx/localization/null_localizer.py +16 -0
- vmx-2.6.0/src/vmx/messages/__init__.py +44 -0
- vmx-2.6.0/src/vmx/messages/collection_changed.py +111 -0
- vmx-2.6.0/src/vmx/messages/construction_status_changed.py +44 -0
- vmx-2.6.0/src/vmx/messages/form_reverted.py +34 -0
- vmx-2.6.0/src/vmx/messages/property_changed.py +45 -0
- vmx-2.6.0/src/vmx/messages/property_value_changed.py +60 -0
- vmx-2.6.0/src/vmx/messages/protocols.py +55 -0
- vmx-2.6.0/src/vmx/messages/tree_structure_changed.py +68 -0
- vmx-2.6.0/src/vmx/notifications/__init__.py +33 -0
- vmx-2.6.0/src/vmx/notifications/confirm_helper.py +25 -0
- vmx-2.6.0/src/vmx/notifications/confirmation_vm.py +92 -0
- vmx-2.6.0/src/vmx/notifications/notification.py +23 -0
- vmx-2.6.0/src/vmx/notifications/notification_hub.py +137 -0
- vmx-2.6.0/src/vmx/notifications/notification_reaction.py +13 -0
- vmx-2.6.0/src/vmx/notifications/notification_type.py +13 -0
- vmx-2.6.0/src/vmx/notifications/notification_vm.py +186 -0
- vmx-2.6.0/src/vmx/notifications/null_notification_hub.py +40 -0
- vmx-2.6.0/src/vmx/properties/__init__.py +25 -0
- vmx-2.6.0/src/vmx/properties/derived.py +222 -0
- vmx-2.6.0/src/vmx/py.typed +0 -0
- vmx-2.6.0/src/vmx/services/__init__.py +29 -0
- vmx-2.6.0/src/vmx/services/dispatcher.py +83 -0
- vmx-2.6.0/src/vmx/services/message_hub.py +82 -0
- vmx-2.6.0/src/vmx/services/null_dispatcher.py +32 -0
- vmx-2.6.0/src/vmx/services/null_message_hub.py +77 -0
- vmx-2.6.0/src/vmx/tree/__init__.py +10 -0
- vmx-2.6.0/src/vmx/tree/walk.py +65 -0
- vmx-2.6.0/tests/__init__.py +0 -0
- vmx-2.6.0/tests/conformance/README.md +7 -0
- vmx-2.6.0/tests/conformance/__init__.py +0 -0
- vmx-2.6.0/tests/conformance/fixtures/__init__.py +0 -0
- vmx-2.6.0/tests/conformance/fixtures/loader.py +26 -0
- vmx-2.6.0/tests/conformance/test_aggregate_vm.py +379 -0
- vmx-2.6.0/tests/conformance/test_builders.py +228 -0
- vmx-2.6.0/tests/conformance/test_cap_021_filterable.py +57 -0
- vmx-2.6.0/tests/conformance/test_cap_022_pageable.py +171 -0
- vmx-2.6.0/tests/conformance/test_capabilities.py +580 -0
- vmx-2.6.0/tests/conformance/test_cmd_008_to_011_fluent_commands.py +150 -0
- vmx-2.6.0/tests/conformance/test_col_001_to_004_serviced.py +167 -0
- vmx-2.6.0/tests/conformance/test_col_005_to_009_observable_list.py +203 -0
- vmx-2.6.0/tests/conformance/test_col_010_to_015_observable_dictionary.py +296 -0
- vmx-2.6.0/tests/conformance/test_col_016_to_021_paged_composition.py +242 -0
- vmx-2.6.0/tests/conformance/test_command_decorators.py +219 -0
- vmx-2.6.0/tests/conformance/test_commands.py +144 -0
- vmx-2.6.0/tests/conformance/test_component_vm.py +429 -0
- vmx-2.6.0/tests/conformance/test_composite_vm.py +648 -0
- vmx-2.6.0/tests/conformance/test_derived_properties.py +345 -0
- vmx-2.6.0/tests/conformance/test_dia_001_to_008_dialog_service.py +369 -0
- vmx-2.6.0/tests/conformance/test_expand_collapse.py +128 -0
- vmx-2.6.0/tests/conformance/test_form_001_to_010_form_vm.py +324 -0
- vmx-2.6.0/tests/conformance/test_forwarding.py +219 -0
- vmx-2.6.0/tests/conformance/test_group_vm.py +228 -0
- vmx-2.6.0/tests/conformance/test_hier_001_to_014_hierarchical_vm.py +542 -0
- vmx-2.6.0/tests/conformance/test_hier_015_to_017_hierarchical_vm_builder.py +137 -0
- vmx-2.6.0/tests/conformance/test_hier_018_reparent_guard.py +66 -0
- vmx-2.6.0/tests/conformance/test_hub.py +228 -0
- vmx-2.6.0/tests/conformance/test_lifecycle.py +247 -0
- vmx-2.6.0/tests/conformance/test_localization.py +57 -0
- vmx-2.6.0/tests/conformance/test_modeled_crud.py +139 -0
- vmx-2.6.0/tests/conformance/test_notifications.py +453 -0
- vmx-2.6.0/tests/conformance/test_null_services.py +97 -0
- vmx-2.6.0/tests/conformance/test_property_change.py +72 -0
- vmx-2.6.0/tests/conformance/test_search_filter.py +158 -0
- vmx-2.6.0/tests/conformance/test_threading.py +208 -0
- vmx-2.6.0/tests/conformance/test_tree_utilities.py +133 -0
- vmx-2.6.0/tests/conftest.py +1 -0
- vmx-2.6.0/tests/unit/__init__.py +0 -0
- vmx-2.6.0/tests/unit/aggregates/__init__.py +0 -0
- vmx-2.6.0/tests/unit/aggregates/test_aggregate_vm.py +768 -0
- vmx-2.6.0/tests/unit/capabilities/__init__.py +0 -0
- vmx-2.6.0/tests/unit/capabilities/test_derived_properties.py +31 -0
- vmx-2.6.0/tests/unit/capabilities/test_expand_collapse.py +29 -0
- vmx-2.6.0/tests/unit/capabilities/test_search_filter.py +75 -0
- vmx-2.6.0/tests/unit/collections/__init__.py +0 -0
- vmx-2.6.0/tests/unit/collections/test_observable_dictionary.py +400 -0
- vmx-2.6.0/tests/unit/collections/test_observable_list.py +536 -0
- vmx-2.6.0/tests/unit/collections/test_paged_composition.py +297 -0
- vmx-2.6.0/tests/unit/collections/test_serviced_observable_collection.py +221 -0
- vmx-2.6.0/tests/unit/commands/__init__.py +0 -0
- vmx-2.6.0/tests/unit/commands/test_decorator_command.py +50 -0
- vmx-2.6.0/tests/unit/commands/test_modeled_crud_commands.py +85 -0
- vmx-2.6.0/tests/unit/commands/test_relay_command.py +223 -0
- vmx-2.6.0/tests/unit/components/__init__.py +0 -0
- vmx-2.6.0/tests/unit/components/test_component_vm.py +519 -0
- vmx-2.6.0/tests/unit/components/test_readonly_component_vm.py +152 -0
- vmx-2.6.0/tests/unit/composites/__init__.py +0 -0
- vmx-2.6.0/tests/unit/composites/test_composite_vm.py +676 -0
- vmx-2.6.0/tests/unit/composites/test_modeled_composite_vm.py +224 -0
- vmx-2.6.0/tests/unit/dialogs/__init__.py +0 -0
- vmx-2.6.0/tests/unit/dialogs/test_null_dialog_service.py +217 -0
- vmx-2.6.0/tests/unit/forms/__init__.py +0 -0
- vmx-2.6.0/tests/unit/forms/test_form_vm.py +311 -0
- vmx-2.6.0/tests/unit/forwarding/__init__.py +0 -0
- vmx-2.6.0/tests/unit/forwarding/test_forwarding.py +333 -0
- vmx-2.6.0/tests/unit/groups/__init__.py +0 -0
- vmx-2.6.0/tests/unit/groups/test_group_vm.py +551 -0
- vmx-2.6.0/tests/unit/helpers/__init__.py +0 -0
- vmx-2.6.0/tests/unit/helpers/test_dispatcher.py +32 -0
- vmx-2.6.0/tests/unit/hierarchical/__init__.py +0 -0
- vmx-2.6.0/tests/unit/hierarchical/test_hierarchical_vm.py +303 -0
- vmx-2.6.0/tests/unit/lifecycle/__init__.py +0 -0
- vmx-2.6.0/tests/unit/lifecycle/test_exceptions.py +34 -0
- vmx-2.6.0/tests/unit/lifecycle/test_status.py +40 -0
- vmx-2.6.0/tests/unit/lifecycle/test_transition_validator.py +68 -0
- vmx-2.6.0/tests/unit/messages/__init__.py +0 -0
- vmx-2.6.0/tests/unit/messages/test_messages.py +112 -0
- vmx-2.6.0/tests/unit/messages/test_property_value_changed.py +104 -0
- vmx-2.6.0/tests/unit/notifications/__init__.py +0 -0
- vmx-2.6.0/tests/unit/notifications/test_confirmation_vm.py +184 -0
- vmx-2.6.0/tests/unit/notifications/test_notification_vm.py +206 -0
- vmx-2.6.0/tests/unit/services/__init__.py +0 -0
- vmx-2.6.0/tests/unit/services/test_message_hub.py +99 -0
- vmx-2.6.0/tests/unit/services/test_null_services_typing.py +96 -0
- vmx-2.6.0/tests/unit/services/test_rx_dispatcher.py +58 -0
- vmx-2.6.0/tests/unit/test_smoke.py +28 -0
vmx-2.6.0/.gitignore
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# ─── macOS ────────────────────────────────────────────────────────────
|
|
2
|
+
.DS_Store
|
|
3
|
+
|
|
4
|
+
# ─── IDE / editor ─────────────────────────────────────────────────────
|
|
5
|
+
.idea/
|
|
6
|
+
.vscode/
|
|
7
|
+
*.swp
|
|
8
|
+
*.swo
|
|
9
|
+
*~
|
|
10
|
+
|
|
11
|
+
# ─── Claude Code session state ─────────────────────────────────────────
|
|
12
|
+
.claude/
|
|
13
|
+
CLAUDE.md
|
|
14
|
+
docs/superpowers/
|
|
15
|
+
|
|
16
|
+
# ─── Python ───────────────────────────────────────────────────────────
|
|
17
|
+
__pycache__/
|
|
18
|
+
*.py[cod]
|
|
19
|
+
*$py.class
|
|
20
|
+
*.so
|
|
21
|
+
.Python
|
|
22
|
+
build/
|
|
23
|
+
develop-eggs/
|
|
24
|
+
dist/
|
|
25
|
+
downloads/
|
|
26
|
+
eggs/
|
|
27
|
+
.eggs/
|
|
28
|
+
sdist/
|
|
29
|
+
wheels/
|
|
30
|
+
share/python-wheels/
|
|
31
|
+
*.egg-info/
|
|
32
|
+
*.egg
|
|
33
|
+
MANIFEST
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
htmlcov/
|
|
37
|
+
.tox/
|
|
38
|
+
.nox/
|
|
39
|
+
.coverage
|
|
40
|
+
.coverage.*
|
|
41
|
+
.cache
|
|
42
|
+
coverage.xml
|
|
43
|
+
*.cover
|
|
44
|
+
.pytest_cache/
|
|
45
|
+
.mypy_cache/
|
|
46
|
+
.dmypy.json
|
|
47
|
+
.ruff_cache/
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.env
|
|
50
|
+
.venv
|
|
51
|
+
env/
|
|
52
|
+
venv/
|
|
53
|
+
ENV/
|
|
54
|
+
|
|
55
|
+
# uv
|
|
56
|
+
# uv.lock is not committed in this repo. The vmx library (langs/python/) is a
|
|
57
|
+
# library, not an application, so consumers always re-resolve. The examples
|
|
58
|
+
# under examples/python/ are tiny demo apps — re-resolving is fine for them too.
|
|
59
|
+
uv.lock
|
|
60
|
+
uv.lock.bak
|
|
61
|
+
|
|
62
|
+
# ─── .NET / C# ────────────────────────────────────────────────────────
|
|
63
|
+
bin/
|
|
64
|
+
obj/
|
|
65
|
+
*.user
|
|
66
|
+
*.suo
|
|
67
|
+
*.userosscache
|
|
68
|
+
*.sln.docstates
|
|
69
|
+
TestResults/
|
|
70
|
+
[Bb]uild[Ll]og.*
|
|
71
|
+
*.[Cc]ache
|
|
72
|
+
project.lock.json
|
|
73
|
+
project.fragment.lock.json
|
|
74
|
+
artifacts/
|
|
75
|
+
.vs/
|
|
76
|
+
|
|
77
|
+
# NuGet
|
|
78
|
+
*.nupkg
|
|
79
|
+
*.snupkg
|
|
80
|
+
.nuget/
|
|
81
|
+
|
|
82
|
+
# ─── Swift ────────────────────────────────────────────────────────────
|
|
83
|
+
# SwiftPM build state (transient — regenerated by `swift build` / `swift test`).
|
|
84
|
+
.build/
|
|
85
|
+
# SwiftPM resolved dependency graph. The repo's only Swift target has no
|
|
86
|
+
# external dependencies today, so Package.resolved would be empty; if deps
|
|
87
|
+
# are added later, remove this line to start committing the lockfile.
|
|
88
|
+
Package.resolved
|
|
89
|
+
# Xcode-derived files (xcuserdata, breakpoints, schemes/xcuserdata). Project
|
|
90
|
+
# itself (.xcodeproj/) is Package-managed and would not normally exist.
|
|
91
|
+
xcuserdata/
|
|
92
|
+
*.xcscmblueprint
|
|
93
|
+
*.xccheckout
|
|
94
|
+
|
|
95
|
+
# ─── Node / TypeScript ────────────────────────────────────────────────
|
|
96
|
+
node_modules/
|
|
97
|
+
*.log
|
|
98
|
+
npm-debug.log*
|
|
99
|
+
yarn-debug.log*
|
|
100
|
+
yarn-error.log*
|
|
101
|
+
.npm/
|
|
102
|
+
.pnpm-store/
|
|
103
|
+
# The library lockfile (langs/typescript/package-lock.json) and the two
|
|
104
|
+
# flagship example lockfiles (examples/typescript/console/hello-vmx,
|
|
105
|
+
# examples/typescript/react/notes-showcase) ARE tracked. Any future ad-hoc
|
|
106
|
+
# example projects placed directly under examples/typescript/<dir>/ are
|
|
107
|
+
# excluded by the glob below (single-* doesn't recurse, so the two committed
|
|
108
|
+
# examples one level deeper are unaffected).
|
|
109
|
+
examples/typescript/*/package-lock.json
|
|
110
|
+
|
|
111
|
+
# ─── Build / coverage artifacts ───────────────────────────────────────
|
|
112
|
+
coverage/
|
|
113
|
+
*.coverage
|
|
114
|
+
*.lcov
|
|
115
|
+
*.cobertura.xml
|
|
116
|
+
|
|
117
|
+
# ─── Docs builds ──────────────────────────────────────────────────────
|
|
118
|
+
docs/_build/
|
|
119
|
+
docs/site/
|
|
120
|
+
site/
|
vmx-2.6.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the Python flavor are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [2.6.0] — 2026-06-13
|
|
10
|
+
|
|
11
|
+
Implements `spec-v2.6.0`. Adds two declarative selection hooks to the
|
|
12
|
+
composite builders, plus four ADRs capturing absorption-audit decisions.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `CompositeVMBuilder[VM].current(selector)` — declarative initial-current
|
|
17
|
+
selector (ADR-0042, COMP-025).
|
|
18
|
+
- `CompositeVMBuilder[VM].on_current_changed(callback)` — synchronous
|
|
19
|
+
post-change selection callback (ADR-0042, COMP-026).
|
|
20
|
+
- Same hooks on the modeled `CompositeVMOfBuilder[M, VM]`.
|
|
21
|
+
|
|
22
|
+
### Documentation
|
|
23
|
+
|
|
24
|
+
- ADR-0039 — `INotifyPropertyChanging` not supported (teaching).
|
|
25
|
+
- ADR-0040 — `IProperty[T]` reactive backing-field not adopted (teaching).
|
|
26
|
+
- ADR-0041 — Single disposable lifecycle, no two-tier bags (teaching).
|
|
27
|
+
- ADR-0042 — `CompositeVMBuilder.current` + `on_current_changed` (behavior change).
|
|
28
|
+
|
|
29
|
+
## [2.5.0] — 2026-06-10
|
|
30
|
+
|
|
31
|
+
Implements `spec-v2.5.0` (ADR-0037).
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- `FormVM.dispose()` is idempotent — a second call raised reactivex
|
|
36
|
+
`DisposedException` (rxjs no-ops, C# guards).
|
|
37
|
+
- `CompositeVM.clear()` routes through the current-selection setter; the old
|
|
38
|
+
current child no longer keeps `is_current == True` with no notification.
|
|
39
|
+
- `PagedComposition` subscribes `on_item_replaced`; `replace()` on the
|
|
40
|
+
current page refreshes `items`.
|
|
41
|
+
- `ObservableList.clear()` emits `PropertyChanged("Count")` after `Reset`
|
|
42
|
+
when the count changed (spec/21 §3.3).
|
|
43
|
+
- `GroupVM` construct/destruct iterate a snapshot so a child lifecycle hook
|
|
44
|
+
that mutates the group cannot skip siblings.
|
|
45
|
+
- A background construct/destruct racing `dispose()` could resurrect the
|
|
46
|
+
VM and publish post-dispose status messages; `DISPOSED` is now terminal
|
|
47
|
+
in `_set_status` and the scheduled work (spec/02 invariant 3).
|
|
48
|
+
- `FormVM.approve_async` no longer raises `DisposedException` when
|
|
49
|
+
`dispose()` runs during the persister await (mirrors the C# guard).
|
|
50
|
+
- `NotificationHub.dispose()` tolerates waiters whose event loop is
|
|
51
|
+
already closed instead of raising and skipping the remaining waiters.
|
|
52
|
+
- `ObservableList.remove_at`/`replace` normalize negative indexes before
|
|
53
|
+
emitting, so the event payload carries the spec-mandated
|
|
54
|
+
index-before-removal instead of a raw `-1` (spec/21 §3.2; TS/C# raise
|
|
55
|
+
on negative indexes by design). Out-of-range negatives raise
|
|
56
|
+
`IndexError` instead of wrapping to a valid index.
|
|
57
|
+
- `FormVM`'s deny path is a no-op after `dispose()` (previously it
|
|
58
|
+
reverted the model and re-published hub messages on a disposed form;
|
|
59
|
+
same guard added in C# and TS). `approve_async()` on a disposed form
|
|
60
|
+
is likewise a full no-op — the persister is no longer invoked.
|
|
61
|
+
- `ObservableList.insert` emits the actual insertion index: in-range
|
|
62
|
+
negatives normalize and out-of-range indexes clamp per stdlib
|
|
63
|
+
`list.insert` semantics, instead of the raw argument leaking into the
|
|
64
|
+
`ItemAdded` payload (spec/21 §3.2). The same normalization applies to
|
|
65
|
+
`ServicedObservableCollection.insert`, `CompositeVM.insert`, and
|
|
66
|
+
`GroupVM.insert` (catalogued vs the C#/TS fail-fast contracts in
|
|
67
|
+
ADR-0009).
|
|
68
|
+
- `FormVM`'s `on_approved` now emits the value that was actually
|
|
69
|
+
persisted rather than the live model (parity with C#'s captured
|
|
70
|
+
payload under a racing `set_model`).
|
|
71
|
+
- `NotificationHub` emits pending snapshots inside the lock (ordering +
|
|
72
|
+
dispose-race discipline, mirroring C#).
|
|
73
|
+
- Post-2.4.0 maintenance backfill: `AggregateVM1..6` dispose ordering
|
|
74
|
+
(LIFE-013) and aggregates walk/dispose drift.
|
|
75
|
+
- `FormVM.builder()` raised `TypeError` on every call (subscripted
|
|
76
|
+
instantiation of a frozen+slots dataclass); it was the only builder
|
|
77
|
+
entrypoint no test had ever exercised.
|
|
78
|
+
- `SearchableState.can_search()` returned `False` when the first item was
|
|
79
|
+
a legal `None` value (sentinel conflation; C#/TS were unaffected).
|
|
80
|
+
- `ConfirmationDecoratorCommand`'s fire-and-forget done-callback raised
|
|
81
|
+
`CancelledError` into the event loop when the task was cancelled.
|
|
82
|
+
- `fluent.confirm()` now types its callback as
|
|
83
|
+
`Callable[[], Awaitable[bool]]`, matching the constructor contract it
|
|
84
|
+
forwards to (a sync callback previously passed mypy and failed at
|
|
85
|
+
`await`).
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- `FORM-014` conformance coverage: a disposed `FormVM` is inert — approve
|
|
90
|
+
never invokes the persister, deny does not revert (ADR-0038; pins the
|
|
91
|
+
guards shipped earlier in this release).
|
|
92
|
+
|
|
93
|
+
- `HierarchicalVM.reparent_child` rejects self- and ancestor-reparenting
|
|
94
|
+
with `ValueError` instead of silently corrupting the tree (HIER-018).
|
|
95
|
+
- `NotificationHub.dispose()` — resolves in-flight waiters with `PENDING`,
|
|
96
|
+
completes `pending`, refuses new enqueues, idempotent (NOTIF-017).
|
|
97
|
+
- The `Dispatcher` protocol is exported from the top-level `vmx` package
|
|
98
|
+
(parity with TS `IDispatcher` / C# `IDispatcher`).
|
|
99
|
+
- Idempotent `dispose()` on `DecoratorCommand` and
|
|
100
|
+
`ConfirmationDecoratorCommand` (teardown symmetry with the C#
|
|
101
|
+
IDisposable surface; the decorators own no subscriptions).
|
|
102
|
+
|
|
103
|
+
## [2.4.0] — 2026-06-02
|
|
104
|
+
|
|
105
|
+
Implements spec v2.4.0 — umbrella publication-readiness + Swift flavor
|
|
106
|
+
sibling + example-app theming scenario contract + test-coverage backfill
|
|
107
|
+
(ADR-0036). Purely additive at the surface level; no behaviour changes
|
|
108
|
+
to existing Python APIs.
|
|
109
|
+
|
|
110
|
+
### Added
|
|
111
|
+
|
|
112
|
+
- **ThemeVM scenario contract** (example apps): the v2.4.0
|
|
113
|
+
`spec-v2.4.0` cycle defines a normative shape for example-app
|
|
114
|
+
theming (`ThemeModel` + `ThemeVM : ComponentVMOf[ThemeModel]` +
|
|
115
|
+
per-framework `ThemeAdapter`) built from the existing core
|
|
116
|
+
primitives (`ComponentVMOf[M]`, `DerivedProperty[T]`, `RelayCommand`,
|
|
117
|
+
`MessageHub`). No new core types are introduced; the contract is
|
|
118
|
+
implemented by the Textual Notes-Showcase flagship under
|
|
119
|
+
`examples/python/textual/notes_showcase/`. See
|
|
120
|
+
`spec/proposals/2026-06-02-theme-vm-scenario.md` + ADR-0036 §2.C.
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- **Aggregate parametric test coverage backfill.** The
|
|
125
|
+
`AggregateVM1..6` test suite was expanded with parametric
|
|
126
|
+
per-arity cases for construction, destruction, modeled-hint
|
|
127
|
+
propagation, and dispose-cascade ordering — bringing aggregate-family
|
|
128
|
+
line coverage to **100%** across all six arities. Existing
|
|
129
|
+
Notes-Showcase edge cases (filter / search / paging interaction,
|
|
130
|
+
capability-aware action-bar gating) gained dedicated tests in the
|
|
131
|
+
same pass. No production code changed; tests only.
|
|
132
|
+
|
|
133
|
+
### Conformance
|
|
134
|
+
|
|
135
|
+
- 5 new IDs (`THEME-001..005`); running total goes from 227 to **232**.
|
|
136
|
+
The Python flavor implements `THEME-001..005` as part of the Textual
|
|
137
|
+
Notes-Showcase flagship's conformance suite (the contract is
|
|
138
|
+
scenario-level, not a core library addition).
|
|
139
|
+
|
|
140
|
+
### Min spec version
|
|
141
|
+
|
|
142
|
+
- 2.4.0 (previously 2.3.0).
|
|
143
|
+
|
|
144
|
+
## [2.3.0] — 2026-05-31
|
|
145
|
+
|
|
146
|
+
Implements spec v2.3.0 — builder pattern audit follow-through (ADR-0035).
|
|
147
|
+
Purely additive at the surface level. One behaviour change brings
|
|
148
|
+
`CompositeVMBuilder` and `GroupVMBuilder` into compliance with the
|
|
149
|
+
existing spec §3 contract (see Fixed below); callers that were relying
|
|
150
|
+
on the previously-lazy validation were already buggy.
|
|
151
|
+
|
|
152
|
+
### Added
|
|
153
|
+
|
|
154
|
+
- **`FormVMBuilder`** (`vmx.forms`) — fluent immutable builder for
|
|
155
|
+
`FormVM` with `.initial(...)` and `.persister(...)` required, and
|
|
156
|
+
optional `.hub(...)`, `.strict(bool)`, `.snapshotter(...)`. Validates
|
|
157
|
+
at `build()`. Conformance: `FORM-011..013`. See ADR-0035 §FV1/FV2.
|
|
158
|
+
- **`HierarchicalVMBuilder`** (`vmx.hierarchical`) — fluent immutable
|
|
159
|
+
builder for `HierarchicalVM` with `.model(...)`,
|
|
160
|
+
`.children_factory(...)`, `.services(hub, dispatcher)` required, and
|
|
161
|
+
optional `.name(...)`, `.hint(...)`, `.eager_children(bool)`. Adds
|
|
162
|
+
`.with_default_services()` Wither for opt-in implicit defaults.
|
|
163
|
+
Validates at `build()`. Conformance: `HIER-015..017`. See ADR-0035
|
|
164
|
+
§H1/H2/H3.
|
|
165
|
+
- **`with_null_services()`** Wither extension on `ComponentVMBuilder`
|
|
166
|
+
(and friends) — chainable convenience that wires
|
|
167
|
+
`NULL_MESSAGE_HUB` + `NULL_DISPATCHER` in one call, for parity with
|
|
168
|
+
the C# `WithNullServices()` extension. See ADR-0035 §SV1.
|
|
169
|
+
- **Typed-arity DerivedProperty factories** — `DerivedProperty.from_one`
|
|
170
|
+
through `DerivedProperty.from_five` with per-source type inference;
|
|
171
|
+
`DerivedProperty.from_many` retained as alias of the existing
|
|
172
|
+
`from_sources(...)` for arbitrary-N consumers. See ADR-0035 §DP2.
|
|
173
|
+
|
|
174
|
+
### Fixed
|
|
175
|
+
|
|
176
|
+
- `CompositeVMBuilder.build()` and `GroupVMBuilder.build()` now raise
|
|
177
|
+
`BuilderValidationError` when `children` is unset, matching the
|
|
178
|
+
spec/10 §3 contract and the TypeScript flavor's existing behaviour.
|
|
179
|
+
Previously the Python flavor silently accepted a missing `children`
|
|
180
|
+
factory and raised later at `on_construct`. See ADR-0035 §CP1/GR2.
|
|
181
|
+
|
|
182
|
+
### Conformance
|
|
183
|
+
|
|
184
|
+
- 7 new IDs (`BLD-005`, `FORM-011..013`, `HIER-015..017`); running total
|
|
185
|
+
goes from 220 to 227.
|
|
186
|
+
|
|
187
|
+
### Min spec version
|
|
188
|
+
|
|
189
|
+
- 2.3.0 (previously 2.2.0).
|
|
190
|
+
|
|
191
|
+
## [2.2.0] — 2026-05-30
|
|
192
|
+
|
|
193
|
+
### Added
|
|
194
|
+
|
|
195
|
+
- `AggregateVM6` — sixth-arity heterogeneous aggregate.
|
|
196
|
+
Conformance: `AGG-006`. See ADR-0034.
|
|
197
|
+
|
|
198
|
+
### Conformance
|
|
199
|
+
|
|
200
|
+
- 1 new ID (`AGG-006`); running total: 220.
|
|
201
|
+
|
|
202
|
+
### Min spec version
|
|
203
|
+
|
|
204
|
+
- 2.2.0 (previously 2.1.0).
|
|
205
|
+
|
|
206
|
+
## [2.1.0] — 2026-05-28
|
|
207
|
+
|
|
208
|
+
Implements spec v2.1.0. Purely additive — no breaking changes from v2.0.x.
|
|
209
|
+
|
|
210
|
+
### Added
|
|
211
|
+
|
|
212
|
+
- **`HierarchicalVM`** (`vmx.hierarchical`) — first-class recursive tree VM with
|
|
213
|
+
lazy/eager child loading, depth-first construction, materialized path,
|
|
214
|
+
parent-change and structural-change hub messages. `TreeStructureChangedMessage`
|
|
215
|
+
new type. (ADR-0028; HIER-001..014)
|
|
216
|
+
- **`DialogService`** + **`NullDialogService`** (`vmx.dialogs`) — host-side
|
|
217
|
+
contract for modal interactions (file pick, confirm prompt, severity-tagged
|
|
218
|
+
notify) distinct from `INotificationHub`. (ADR-0029; DIA-001..008)
|
|
219
|
+
- **`FormVM`** (`vmx.forms`) — snapshot/revert edit lifecycle (ORM-agnostic).
|
|
220
|
+
`deny_command`, `approve_command`, `on_approved` event, optional strict mode.
|
|
221
|
+
`FormRevertedMessage` new type. (ADR-0030; FORM-001..010)
|
|
222
|
+
- **`NotificationVM`** + **`ConfirmationVM`** (`vmx.notifications`) — render-side
|
|
223
|
+
VMs with auto-dismiss (60s/300s default), opacity decay, dismiss/approve/reject
|
|
224
|
+
commands. (ADR-0031; NOTIF-011..016)
|
|
225
|
+
- **`ServicedObservableCollection`** (`vmx.collections`) — observable collection
|
|
226
|
+
with hub publication. (ADR-0024; COL-001..004)
|
|
227
|
+
- **`ObservableList`** (`vmx.collections`) — granular per-mutation events
|
|
228
|
+
(item_added/removed/replaced/reset) with batch suppression. (ADR-0026;
|
|
229
|
+
COL-005..009, COL-023)
|
|
230
|
+
- **`ObservableDictionary`** (`vmx.collections`) — composite-key observable
|
|
231
|
+
dictionary with observable keys1/keys2 views and hub publication. (ADR-0025;
|
|
232
|
+
COL-010..015, COL-022)
|
|
233
|
+
- **`PagedComposition`** (`vmx.collections`) — paging decorator over any
|
|
234
|
+
composition implementing `Pageable`. (ADR-0023; COL-016..021)
|
|
235
|
+
- **`Filterable`** + **`Pageable`** (`vmx.capabilities`) — two new capability
|
|
236
|
+
protocols. (ADR-0022, ADR-0023; CAP-021, CAP-022)
|
|
237
|
+
- **Fluent command helpers** (`vmx.commands`) — `confirm(…)`, `precede_with`,
|
|
238
|
+
`succeed_with`, `wrap_with` extension helpers over commands. (ADR-0027;
|
|
239
|
+
CMD-008..011)
|
|
240
|
+
- **`property_value_changed_messages_for`** helper (`vmx.messages`) —
|
|
241
|
+
function that filters `PropertyChangedMessage` events for a given
|
|
242
|
+
sender + property name and returns an observable stream of the
|
|
243
|
+
property's value snapshots. (ADR-0032; informative)
|
|
244
|
+
- **Conformance**: 67 new IDs (total 219).
|
|
245
|
+
|
|
246
|
+
### Fixed
|
|
247
|
+
|
|
248
|
+
- `CompositeVM.__setitem__` now clears `current` to None when the
|
|
249
|
+
replaced slot held the current selection, mirroring `_remove_at`.
|
|
250
|
+
Previously `_current` would silently dangle on the removed child.
|
|
251
|
+
- `AggregateVM1.._on_construct` now disposes the previous slot
|
|
252
|
+
instance before invoking the factory on Reconstruct, so the old
|
|
253
|
+
VM's hub subscriptions and command Subjects are released instead of
|
|
254
|
+
lingering until the hub itself is disposed. (Parity with the C# fix.)
|
|
255
|
+
- `NotificationHub.resolve()` now schedules `future.set_result` via
|
|
256
|
+
`loop.call_soon_threadsafe`, making `resolve()` safe to call from a
|
|
257
|
+
thread other than the future's owning event loop (`asyncio.Future`
|
|
258
|
+
itself is not thread-safe).
|
|
259
|
+
- `CompositeCommand.dispose()` no longer iterates a permanently-empty
|
|
260
|
+
`_subscriptions` list (dead state). The merged `can_execute_changed`
|
|
261
|
+
observable is lazy; subscribers' own disposables tear down the
|
|
262
|
+
merged chain when they unsubscribe.
|
|
263
|
+
- `SearchableState.search_term` setter no longer pushes the new value
|
|
264
|
+
through the debounce/recompute pipeline when it equals the current
|
|
265
|
+
value (spec wording: "emission on a new value").
|
|
266
|
+
- `SearchableState.can_search` now uses `next(iter(...), None) is not None`
|
|
267
|
+
instead of `any(True for _ in ...)`, materialising one element
|
|
268
|
+
instead of the entire iterable.
|
|
269
|
+
- `DecoratorCommand.execute` now wraps the inner `execute` call in
|
|
270
|
+
try/finally so the `post_execute` callback always runs — a "busy"
|
|
271
|
+
flag set in `pre_execute` no longer gets stuck when the inner
|
|
272
|
+
command raises.
|
|
273
|
+
|
|
274
|
+
### Changed
|
|
275
|
+
|
|
276
|
+
- `DerivedProperty`, `SearchableState`, and `ExpandableState` `dispose()`
|
|
277
|
+
methods now call `.dispose()` after `.on_completed()` on each Subject,
|
|
278
|
+
matching the project-wide pattern in `MessageHub` and `RelayCommand`.
|
|
279
|
+
- `properties.derived._apply` uses `cast()` instead of
|
|
280
|
+
`assert isinstance(values, tuple)` so the runtime guard is not
|
|
281
|
+
stripped by `python -O`.
|
|
282
|
+
|
|
283
|
+
## [2.0.0] — 2026-05-25
|
|
284
|
+
|
|
285
|
+
Implements spec v2.0.0 — capability micro-interfaces, derived properties,
|
|
286
|
+
search/filter, expand/collapse, modeled-CRUD commands, null-object services,
|
|
287
|
+
opt-in notifications sub-package, and a localization hook.
|
|
288
|
+
|
|
289
|
+
### Added
|
|
290
|
+
- **Capabilities** (`vmx.capabilities`): 20 opt-in micro-interfaces —
|
|
291
|
+
`ISelectable`, `IDeselectable`, `ISelectionTogglable`, `IExpandable`,
|
|
292
|
+
`ICollapsible`, `IExpansionTogglable`, `ISearchable`, `IClosable`,
|
|
293
|
+
`IApprovable`, `ICancelable`, `INewCreatable`, `IDeletable`,
|
|
294
|
+
`IUpdatable`, `ISavable`, `ICurrentDeletable`, `ICurrentUpdatable`,
|
|
295
|
+
`IManagable`, `IConstructable`, `IDestructable`, `IReconstructable`
|
|
296
|
+
(see `src/vmx/capabilities/`).
|
|
297
|
+
- **Helpers** (`vmx.capabilities`): `SearchableState[TItem]` (debounced
|
|
298
|
+
filter), `ExpandableState` (expand/collapse + observable change).
|
|
299
|
+
- **Derived properties** (`vmx.properties`): `DerivedProperty[TValue]` +
|
|
300
|
+
`from_sources(*sources, transform)` factory for N-source computed values
|
|
301
|
+
with `distinct_until_changed` + optional write-back.
|
|
302
|
+
- **Commands**: `ConfirmationDecoratorCommand` + the abstract
|
|
303
|
+
`DecoratorCommand` base, `make_confirm` helper,
|
|
304
|
+
`ModeledCrudCommands[M, VM]` for the CRUD trio
|
|
305
|
+
(create / update_current / delete_current) on modeled composites.
|
|
306
|
+
- **Null-object services** (per ADR-0017): `NullMessageHub`, `NullDispatcher`,
|
|
307
|
+
`NullLocalizer`, plus `NullNotificationHub` (in the notifications package).
|
|
308
|
+
- **Localization** (`vmx.localization`): `ILocalizer` Protocol and
|
|
309
|
+
`NullLocalizer` (identity translator) — the only opinionated localizer
|
|
310
|
+
shipped in core.
|
|
311
|
+
- **Notifications sub-package** (`vmx.notifications`, opt-in): `Notification`,
|
|
312
|
+
`NotificationType`, `NotificationReaction`, `INotificationHub` +
|
|
313
|
+
`NotificationHub` reference impl + `NullNotificationHub`.
|
|
314
|
+
- **Tree utilities**: `walk_expanded(root)` — variant of `walk` that only
|
|
315
|
+
descends into expanded composites (uses the new `IExpandable` capability).
|
|
316
|
+
- **Conformance**: 77 new IDs (`CAP-NNN`, `DPROP-NNN`, `NOTIF-NNN`,
|
|
317
|
+
`LOC-NNN`, `COMP-014..024`, `GRP-007..010`) — total now 152 IDs.
|
|
318
|
+
|
|
319
|
+
### Internal
|
|
320
|
+
- `vmx.builders._validation.require_field` / `require_services` return
|
|
321
|
+
narrowed values for tighter mypy --strict downstream typing.
|
|
322
|
+
- Dispose paths across `Modeled*` / `Searchable*` / `Expandable*` /
|
|
323
|
+
`Derived*` are guarded with `_disposed` for idempotence.
|
|
324
|
+
|
|
325
|
+
### Notes
|
|
326
|
+
- The legacy aliases `RelayCommandOfT` / `RelayCommandOfTBuilder` and
|
|
327
|
+
`AggregateVMBuilder1..5` continue to ship in v2.0.0; their removal has
|
|
328
|
+
been deferred to **vmx v3.0.0** (next major). See ADR-0009.
|
|
329
|
+
|
|
330
|
+
## [1.2.0] — 2026-05-23
|
|
331
|
+
|
|
332
|
+
### Added
|
|
333
|
+
- `RelayCommandOf` and `RelayCommandOfBuilder` are now the canonical names for
|
|
334
|
+
the parameterised command + builder pair, matching the TypeScript flavor's
|
|
335
|
+
`RelayCommandOf` / `RelayCommandOfBuilder`.
|
|
336
|
+
- `AggregateVM1Builder` through `AggregateVM5Builder` are now the canonical
|
|
337
|
+
builder names for the aggregate VMs, matching the TypeScript flavor's
|
|
338
|
+
`AggregateVMNBuilder` shape.
|
|
339
|
+
|
|
340
|
+
### Deprecated
|
|
341
|
+
- `RelayCommandOfT` and `RelayCommandOfTBuilder` remain as identity aliases for
|
|
342
|
+
backward compatibility. Removal deferred to **vmx v3.0.0** (was originally
|
|
343
|
+
targeted for v2.0.0; see v2.0.0 Notes and ADR-0009).
|
|
344
|
+
- `AggregateVMBuilder1` through `AggregateVMBuilder5` remain as identity aliases
|
|
345
|
+
for backward compatibility. Removal deferred to **vmx v3.0.0** (was originally
|
|
346
|
+
targeted for v2.0.0; see v2.0.0 Notes and ADR-0009).
|
|
347
|
+
|
|
348
|
+
### Internal
|
|
349
|
+
- Per-suppression rationale comments added at every `# type: ignore` in
|
|
350
|
+
`vmx.forwarding.composite` and `vmx.components.builders` (10 + 2 sites).
|
|
351
|
+
- `vmx.builders._validation` now declares parameters as `object | None` instead
|
|
352
|
+
of `Any`, with a module docstring explaining why a Hub/Dispatcher Protocol is
|
|
353
|
+
intentionally not used.
|
|
354
|
+
- `vmx.components.base` empty B027-silenced override hooks now carry an inline
|
|
355
|
+
reason in their `noqa` comment.
|
|
356
|
+
|
|
357
|
+
## [1.1.0] — 2026-05-23
|
|
358
|
+
|
|
359
|
+
### Added
|
|
360
|
+
- Implements spec-v1.1.0 on top of the v1.0.0 surface.
|
|
361
|
+
- `CompositeVM` / `CompositeVMOf` / `GroupVM`: new `.auto_construct_on_add(bool)` builder option. When `True`, a child added after the container reaches `Constructed` is automatically constructed before the `CollectionChanged(Add)` event fires.
|
|
362
|
+
- `CompositeVM` / `CompositeVMOf` / `GroupVM`: new `batch_update()` method returns a context manager / disposable that suppresses per-mutation `CollectionChanged` events. The outermost handle disposal emits a single `CollectionChanged(Reset)` event iff any mutations occurred during the batch.
|
|
363
|
+
- New `vmx.tree` module with `walk(root)` (DFS pre-order generator) and `find(root, predicate)` (short-circuiting first-match).
|
|
364
|
+
- New conformance IDs: COMP-012, COMP-013, GRP-005, GRP-006, UTIL-001, UTIL-002, UTIL-003 (75/75 catalog coverage).
|
|
365
|
+
- Top-level `vmx.collections` module hosting the canonical `CollectionChangedEvent` (unified across composites and groups).
|
|
366
|
+
- Top-level `vmx` re-exports for the most-used types (`from vmx import ComponentVMOf, MessageHub, RxDispatcher, walk, find`).
|
|
367
|
+
|
|
368
|
+
### Fixed
|
|
369
|
+
- `GroupVM.dispose()` now cascades depth-first, matching the spec's LIFE-013 contract and the C# behavior.
|
|
370
|
+
- `CompositeVM` factory children now emit `CollectionChanged(Add)` events (previously silent), matching C#.
|
|
371
|
+
- Removed a stale "scaffolding state / Phase 3" docstring from `vmx/__init__.py`.
|
|
372
|
+
|
|
373
|
+
## [1.0.0] — 2026-05-23
|
|
374
|
+
|
|
375
|
+
### Added
|
|
376
|
+
- Full implementation of spec-v1.0.0:
|
|
377
|
+
- Lifecycle: `ConstructionStatus` IntEnum + `StatusTransitionError` + JSON-fixture-backed transition validator
|
|
378
|
+
- Messages: `Message`/`TypedMessage` Protocols + `PropertyChangedMessage` + `ConstructionStatusChangedMessage` frozen dataclasses
|
|
379
|
+
- Services: `MessageHub` (Subject-backed hot stream with per-subscription exception isolation) + `Dispatcher` Protocol + `RxDispatcher` (with `immediate()` and `asyncio(loop)` factories)
|
|
380
|
+
- Commands: `RelayCommand` + `RelayCommandOfT[T]` with reactive triggers and immutable frozen-dataclass builders; Execute is gated on can_execute
|
|
381
|
+
- Components: `ComponentVM`, `ComponentVMOf[M]`, `ReadonlyComponentVMOf[M]` with full lifecycle, modeled hint, 5 built-in commands, async variants
|
|
382
|
+
- Composites: `CompositeVM[VM]` + `CompositeVMOf[M, VM]` with selection contract, MutableSequence + Observable[CollectionChangedEvent], async-selection dispatch
|
|
383
|
+
- Groups: `GroupVM[VM]` (children-as-peers; no Current; retains SelectCommand/DeselectCommand)
|
|
384
|
+
- Aggregates: `AggregateVM1`..`AggregateVM5` fixed-arity tuples
|
|
385
|
+
- Forwarding: `ForwardingComponentVM[M]` + `ForwardingCompositeVM[VM]` decorators
|
|
386
|
+
- Background option dispatches construct/destruct on `Dispatcher.background` scheduler
|
|
387
|
+
- 68 conformance tests covering LIFE-001..013, HUB-001..007, PROP-001..004, CMD-001..007, CVM-001..006, COMP-001..011, GRP-001..004, AGG-001..005, FWD-001..003, BLD-001..004, THR-001..004 — all pass.
|
|
388
|
+
- 376+ unit tests across all modules — all pass.
|
|
389
|
+
- Python 3.10–3.13 supported.
|
|
390
|
+
- `mypy --strict` clean across the entire `src/vmx/` tree.
|
|
391
|
+
- Examples: `examples/python/hello_vmx/` (console) and `examples/python/tk_todo_app/` (tkinter MVVM).
|
|
392
|
+
- Getting-started tutorial at `docs/getting-started/python.md`.
|