Changelog
All notable changes to this project are documented in this file, grouped by date.
The format is based on Keep a Changelog.
[2026-07-17]
Changed
- Deps: bumped
NSubstitutefrom5.3.0to6.0.0(major). Fixed three test files (CreateCashierCommandHandlerTests,UpdateCashierCommandHandlerTests,CreateInvoiceCommandHandlerTests) that castCallInfo's indexer directly ((T)x[0]), which no longer compiles under 6.0's nullable-annotated public API — switched to the null-safex.ArgAt<T>(0)extension instead.
Skipped
- Deps:
Refitter.MSBuild2.0.0→2.1.0was left in place — 2.1.0 refactored the MSBuild task itself (upstream decoupled the CLI binary from the task) and fails code generation locally withMissingMethodException: System.Text.ValueStringBuilder.AsSpan(). Needs an upstream fix before retrying.
[2026-07-16]
Changed
- Docs: widened both VitePress sites (
docs/,libs/Momentum/docs/) — layout frame now maxes out at 1640px (--vp-layout-max-width) and the doc content column at 900px, up from the VitePress defaults. - Docs: nav "Changelog" link now renders
CHANGELOG.mdinline as a docs page (/changelog) instead of linking out to GitHub.
Fixed
- Docs: Mermaid diagrams now render with the registered ELK layout (
layout: "elk"was never passed tomermaid.render, so the loader was registered but unused). - Docs: fixed 115 Mermaid diagrams across both sites using the invalid arrow
-/->, which failed to parse and silently dropped every affected diagram (most notably all ofdocs/arch/*.md).
Changed
- Docs: Mermaid diagrams now render with the
neolook (was unset, defaulting toclassic). - Docs: the architecture/system diagrams in
docs/arch/*.md(index.md,eda.md,events.md,background-processing.md) now use a consistent C4-style palette — blue for containers, light blue for domain/component-level nodes, grey for external systems (Kafka, third-party services) — replacing the ad-hoc pastelstyleoverrides that only existed on one of the four diagrams. - Deps: bumped
mermaidfrom^11.10.1to^11.16.0(latest) in bothdocs/andlibs/Momentum/docs/. - Docs: Mermaid subgraph/cluster backgrounds are now white instead of the default pale-yellow theme color (light theme only, via
themeVariables.clusterBkg).
[2026-07-15]
Added
- EventMarkdownGenerator:
TopicandFullyQualifiedTopicNameare now exposed separately onEventMetadataandEventViewModel.Topicis the plain topic / event hub name;FullyQualifiedTopicNameis the composed{env}.{domain}.{visibility}.{topic}.{version}convention string. - EventMarkdownGenerator:
--partition-key-attributeoption to discover partition keys by attribute name (or name prefix), mirroring--event-attribute. Partition key discovery and itsOrderare now resolved generically via reflection instead of a hardcodedPartitionKeyAttributetype. - EventMarkdownGenerator: event attributes may expose an optional
EventNameproperty to override the documented event name (falls back to the CLR type name when absent). - Docs: new VitePress guide page for the Event Documentation Generator (
libs/Momentum/docs/guide/messaging/event-documentation.md), covering MSBuild/CLI usage, template customization, and generated output structure.
Changed
- EventMarkdownGenerator:
EventMetadata.TopicName/EventViewModel.TopicNamereplaced byFullyQualifiedTopicName. The defaultevent.liquidtemplate now renders bothTopicandFully Qualified Topic. - EventMarkdownGenerator: multi-line XML doc property descriptions are flattened to a single line (whitespace collapsed) so they no longer break the generated markdown payload table.
Added
- EventMarkdownGenerator:
EventMetadata.EventTypeNameexposes the event's CLR type name separately fromEventName(which may be overridden via the topic attribute'sEventNameproperty). Rendered in generated docs as "Type Name". - EventMarkdownGenerator:
EventMetadata.AttributePropertiescaptures every public property of the discovered topic attribute (via reflection) into a name/value dictionary, so custom attribute properties the generator has no dedicated field for still surface in generated docs, under "Attribute Properties".
Changed
- EventMarkdownGenerator: extracted
EventMetadataBuilderfromAssemblyEventDiscoveryso assembly/type scanning stays independent from reflecting a single event type and its topic attribute intoEventMetadata. - EventMarkdownGenerator: extracted
EventPropertyMetadataBuilderfromEventMetadataBuilderso reflecting an event type's own properties/partition keys stays independent from resolving its topic attribute and computed fields (topic, domain, fully-qualified topic name, etc). - EventMarkdownGenerator: moved generic reflection helpers (
FindAttributeByName,GetPropertyValue<T>,MapConstructorParametersToProperties) that carry no event-metadata-specific meaning fromEventMetadataBuilderintoTypeUtils. Also removed a private kebab-case fallback that duplicated theMomentum.Extensions.Abstractions.Extensions.ToKebabCase()extension already imported in the same file.
Tests
- EventMarkdownGenerator: added an
all-properties-showcasescenario (realTestEventsfixture and XML doc input, checked intoIntegrationTestScenarios/) that exercises every renderableEventMetadataproperty at once — obsolete marker, explicit domain/topic/version, internal visibility, multiple partition keys, a complex nested property, a collection of a complex type, and real Summary/Remarks/Example/param documentation — as a durable, reviewable example alongside the existing marker-value completeness unit test. - EventMarkdownGenerator:
ScenarioBasedIntegrationTestsnow supports a per-scenariotemplates/event.liquidoverride (falling back to the default embedded template when absent). Used byall-properties-showcase, whose override renders every singleEventViewModelfield verbatim — including every entry ofProperties,PartitionKeys, andAttributeProperties— as a raw, exhaustive dump distinct from the polished default rendering.
Added
- EventMarkdownGenerator:
EventMetadata.Entityis now computed once at metadata-build time (moved offEventViewModelFactory). When the topic attribute isn't generic (noTEntityto reflect), it falls back to stripping a common event-verb suffix (Created,Updated,Deleted,Completed,Processed, etc.) off the event type's own name, e.g.WidgetCreated→widget. - EventMarkdownGenerator:
EventMetadata.EventNameKebabexposes a kebab-cased form ofEventName(respecting the sameEventNameattribute override) for topic-adjacent/URL-safe uses, without kebab-casing the human-facingEventName/heading itself. Rendered in generated docs as "Event Slug".
Changed
- EventMarkdownGenerator:
AssemblyEventDiscovery.DiscoverEventsnow returnsIEnumerable<EventWithDocumentation>(metadata paired with its XML documentation) instead of bareEventMetadata, doing thexmlParser.GetEventDocumentation(...)lookup internally. Removes the repeatedevents.Select(m => new EventWithDocumentation { ... })boilerplate every caller (GenerateCommand, tests) previously had to write itself. - EventMarkdownGenerator: removed
EventMetadata.EventType— the raw CLRTypewas only ever used to re-look-up XML documentation or extractEntity, both of which now happen once during metadata construction instead of being deferred to callers. - EventMarkdownGenerator:
FullyQualifiedTopicNameno longer includes a leading{env}.placeholder segment. It was never substituted by the doc generator (a design-time tool with no concept of a deployment environment), so it only ever showed up as a literal, unresolved token in generated docs. Now composed as{domain}.{visibility}.{topic}.{version}. - Docs: synced the
event.liquidvariable table in the new VitePress guide page (libs/Momentum/docs/guide/messaging/event-documentation.md) with the fields and behavior described above — dropped the stale{env}prefix, and addedEventNameKebab,EventTypeName,Domain,Summary, andAttributeProperties.
Fixed
- EventMarkdownGenerator:
FullyQualifiedTopicNameused the assembly-level default domain instead of the event's actually-resolved domain (explicit attributeDomainoverride, then namespace-derived, then the default) — so an event could renderDomain: comprehensive-domainin one field while its fully-qualified topic string still showed the unrelated default domain segment. Both fields now agree. - EventMarkdownGenerator: the domain segment of
FullyQualifiedTopicNamewas only lowercased (ToLowerInvariant()), not kebab-cased, so a multi-word domain likeTestEventsproduced the gluedtesteventsinstead oftest-events. Now usesToKebabCase(), consistent with howTopicandEntityare derived. - EventMarkdownGenerator: property descriptions whose XML doc summary wrapped onto multiple lines previously spilled out of their table cell as an orphaned line.
- EventMarkdownGenerator:
EventMetadata.Domainwas computed but never mapped onto the Liquid view model, so it never appeared in generated docs. Now rendered as "Domain". - EventMarkdownGenerator: the
TopicAttributefield onEventMetadatacould point at a different attribute instance than the one topic/domain/version were actually parsed from (its own lookup usedGetCustomAttributes<Attribute>().FirstOrDefault(), which matches any attribute, not specifically the topic one). It now reuses the single correctly-matched instance throughout. - DummyInvoiceGenerator (sample BackOffice service): resolved a DI lifetime crash on startup — the singleton
BackgroundServicewas constructor-injecting the scopedWolverine.IMessageBusdirectly, which fails ASP.NET Core's DI validation. It now resolvesIMessageBusfrom a newIServiceScopeFactory-created scope on each publish iteration. - AppDomain.BackOffice.Orleans: fixed a startup crash where resolving
GrainDirectory:Defaultwrote an incomplete provider section (ServiceKeywith noProviderType) intoIConfiguration, which Orleans' own configuration-driven provider discovery then tried and failed to auto-register. Grain directory service-name resolution is now read-only, since it's configured purely through the fluentAddAzureTableGrainDirectorycall. - AppDomain.BackOffice.Orleans: fixed Azure Table clustering failing with "No credentials specified" under Docker Compose — the
Clustering:ServiceKey/GrainStorage:Default:ServiceKeyvalues must be written back intoIConfigurationfor Orleans to bind its declarative provider to the correct keyed Azure client, which a prior fix attempt had inadvertently removed. - compose.yml: Azurite now starts with
--skipApiVersionCheck— the Azure SDK clients request the latest Azure Storage service API version, which is routinely ahead of what Azurite has added support for, breaking local Orleans clustering/grain-storage/grain-directory calls out of the box.
Security
- docs-dotnet.ts: the
docfx metadatabuild step now resolves thedocfxexecutable from the fixed.NETglobal-tools directory instead of an unpinnedPATHlookup, closing a SonarCloud security hotspot (typescript:S4036).