com.wallstop-studios.dxmessaging 2.1.5 → 2.1.7

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 (163) hide show
  1. package/.artifacts/SourceGenerators.Tests/obj/Debug/net9.0/WallstopStudios.DxMessaging.SourceGenerators.Tests.AssemblyInfo.cs +1 -1
  2. package/.cspell.json +4 -1
  3. package/.github/workflows/actionlint.yml +11 -1
  4. package/.github/workflows/csharpier-autofix.yml +34 -3
  5. package/.github/workflows/dotnet-tests.yml +13 -0
  6. package/.github/workflows/format-on-demand.yml +38 -44
  7. package/.github/workflows/json-format-check.yml +24 -0
  8. package/.github/workflows/lint-doc-links.yml +13 -0
  9. package/.github/workflows/markdown-json.yml +21 -4
  10. package/.github/workflows/markdown-link-text-check.yml +10 -0
  11. package/.github/workflows/markdown-link-validity.yml +10 -0
  12. package/.github/workflows/markdownlint.yml +7 -5
  13. package/.github/workflows/prettier-autofix.yml +67 -11
  14. package/.github/workflows/release-drafter.yml +2 -2
  15. package/.github/workflows/sync-wiki.yml +3 -3
  16. package/.github/workflows/yaml-format-lint.yml +26 -0
  17. package/.llm/context.md +113 -3
  18. package/.llm/skills/documentation/changelog-management.md +38 -0
  19. package/.llm/skills/documentation/documentation-style-guide.md +18 -0
  20. package/.llm/skills/documentation/documentation-update-workflow.md +2 -0
  21. package/.llm/skills/documentation/documentation-updates.md +2 -0
  22. package/.llm/skills/documentation/markdown-compatibility.md +476 -0
  23. package/.llm/skills/documentation/mermaid-theming.md +326 -0
  24. package/.llm/skills/documentation/mkdocs-navigation.md +290 -0
  25. package/.llm/skills/github-actions/git-renormalize-patterns.md +231 -0
  26. package/.llm/skills/github-actions/workflow-consistency.md +346 -0
  27. package/.llm/skills/index.md +53 -27
  28. package/.llm/skills/scripting/javascript-code-quality.md +417 -0
  29. package/.llm/skills/scripting/regex-documentation.md +461 -0
  30. package/.llm/skills/scripting/shell-best-practices.md +55 -4
  31. package/.llm/skills/scripting/validation-patterns.md +418 -0
  32. package/.llm/skills/specification.md +4 -1
  33. package/.llm/skills/testing/test-code-quality.md +243 -0
  34. package/.llm/skills/testing/test-production-code.md +348 -0
  35. package/CHANGELOG.md +24 -0
  36. package/README.md +113 -24
  37. package/Tests/Runtime/Benchmarks/WallstopStudios.DxMessaging.Tests.Runtime.Benchmarks.asmdef +1 -6
  38. package/Tests/Runtime/Integrations/Reflex/WallstopStudios.DxMessaging.Tests.Runtime.Reflex.asmdef +1 -1
  39. package/Tests/Runtime/Integrations/VContainer/WallstopStudios.DxMessaging.Tests.Runtime.VContainer.asmdef +1 -1
  40. package/Tests/Runtime/Integrations/Zenject/WallstopStudios.DxMessaging.Tests.Runtime.Zenject.asmdef +1 -1
  41. package/coverage/clover.xml +216 -3
  42. package/coverage/clover.xml.meta +7 -7
  43. package/coverage/coverage-final.json +2 -1
  44. package/coverage/coverage-final.json.meta +7 -7
  45. package/coverage/lcov-report/base.css.meta +1 -1
  46. package/coverage/lcov-report/block-navigation.js.meta +1 -1
  47. package/coverage/lcov-report/favicon.png.meta +1 -1
  48. package/coverage/lcov-report/index.html +25 -10
  49. package/coverage/lcov-report/index.html.meta +7 -7
  50. package/coverage/lcov-report/prettify.css.meta +1 -1
  51. package/coverage/lcov-report/prettify.js.meta +1 -1
  52. package/coverage/lcov-report/sort-arrow-sprite.png.meta +1 -1
  53. package/coverage/lcov-report/sorter.js.meta +1 -1
  54. package/coverage/lcov-report/transform-docs-to-wiki.js.html +1 -1
  55. package/coverage/lcov-report/transform-docs-to-wiki.js.html.meta +7 -7
  56. package/coverage/lcov-report/vendor.meta +1 -1
  57. package/coverage/lcov-report.meta +8 -8
  58. package/coverage/lcov.info +365 -0
  59. package/coverage/lcov.info.meta +7 -7
  60. package/docs/architecture/design-and-architecture.md +0 -1
  61. package/docs/concepts/index.md +37 -0
  62. package/docs/concepts/index.md.meta +7 -0
  63. package/docs/concepts/interceptors-and-ordering.md +0 -2
  64. package/docs/concepts/mental-model.md +390 -0
  65. package/docs/concepts/mental-model.md.meta +7 -0
  66. package/docs/concepts/message-types.md +0 -1
  67. package/docs/getting-started/getting-started.md +1 -0
  68. package/docs/getting-started/index.md +6 -5
  69. package/docs/getting-started/overview.md +1 -0
  70. package/docs/getting-started/quick-start.md +2 -1
  71. package/docs/getting-started/visual-guide.md +4 -10
  72. package/docs/hooks.py +10 -1
  73. package/docs/images/DxMessaging-banner.svg +1 -1
  74. package/docs/index.md +7 -7
  75. package/docs/javascripts/mermaid-config.js +44 -4
  76. package/docs/reference/helpers.md +130 -154
  77. package/docs/reference/quick-reference.md +5 -1
  78. package/docs/reference/reference.md +124 -130
  79. package/mkdocs.yml +2 -0
  80. package/package.json +7 -2
  81. package/scripts/__tests__/generate-skills-index.test.js +397 -0
  82. package/scripts/__tests__/generate-skills-index.test.js.meta +7 -0
  83. package/scripts/__tests__/mermaid-config.test.js +467 -0
  84. package/scripts/__tests__/mermaid-config.test.js.meta +7 -0
  85. package/scripts/__tests__/validate-skills-optional-fields.test.js +1474 -0
  86. package/scripts/__tests__/validate-skills-optional-fields.test.js.meta +7 -0
  87. package/scripts/__tests__/validate-skills-required-fields.test.js +188 -0
  88. package/scripts/__tests__/validate-skills-required-fields.test.js.meta +7 -0
  89. package/scripts/__tests__/validate-skills-tags.test.js +353 -0
  90. package/scripts/__tests__/validate-skills-tags.test.js.meta +7 -0
  91. package/scripts/__tests__/validate-workflows.test.js +188 -0
  92. package/scripts/__tests__/validate-workflows.test.js.meta +7 -0
  93. package/scripts/generate-skills-index.js +88 -3
  94. package/scripts/validate-skills.js +230 -30
  95. package/scripts/validate-workflows.js +272 -0
  96. package/scripts/validate-workflows.js.meta +7 -0
  97. package/scripts/wiki/generate-wiki-sidebar.js.meta +1 -8
  98. package/scripts/wiki/transform-docs-to-wiki.js.meta +1 -1
  99. package/site/404.html +1 -1
  100. package/site/advanced/emit-shorthands/index.html +2 -2
  101. package/site/advanced/message-bus-providers/index.html +2 -2
  102. package/site/advanced/registration-builders/index.html +2 -2
  103. package/site/advanced/runtime-configuration/index.html +2 -2
  104. package/site/advanced/string-messages/index.html +2 -2
  105. package/site/advanced.meta +1 -1
  106. package/site/architecture/comparisons/index.html +2 -2
  107. package/site/architecture/design-and-architecture/index.html +2 -2
  108. package/site/architecture/performance/index.html +1 -1
  109. package/site/architecture.meta +1 -1
  110. package/site/concepts/index.html +1 -0
  111. package/site/concepts/index.html.meta +7 -0
  112. package/site/concepts/interceptors-and-ordering/index.html +4 -4
  113. package/site/concepts/listening-patterns/index.html +2 -2
  114. package/site/concepts/mental-model/index.html +146 -0
  115. package/site/concepts/mental-model/index.html.meta +7 -0
  116. package/site/concepts/mental-model.meta +8 -0
  117. package/site/concepts/message-types/index.html +2 -2
  118. package/site/concepts/targeting-and-context/index.html +2 -2
  119. package/site/concepts.meta +1 -1
  120. package/site/examples/end-to-end/index.html +2 -2
  121. package/site/examples/end-to-end-scene-transitions/index.html +2 -2
  122. package/site/examples.meta +1 -1
  123. package/site/getting-started/getting-started/index.html +3 -3
  124. package/site/getting-started/index.html +4 -4
  125. package/site/getting-started/install/index.html +3 -3
  126. package/site/getting-started/overview/index.html +2 -2
  127. package/site/getting-started/quick-start/index.html +2 -2
  128. package/site/getting-started/visual-guide/index.html +11 -11
  129. package/site/getting-started.meta +1 -1
  130. package/site/guides/advanced/index.html +2 -2
  131. package/site/guides/diagnostics/index.html +2 -2
  132. package/site/guides/migration-guide/index.html +2 -2
  133. package/site/guides/patterns/index.html +2 -2
  134. package/site/guides/testing/index.html +2 -2
  135. package/site/guides/unity-integration/index.html +2 -2
  136. package/site/guides.meta +1 -1
  137. package/site/hooks.py.meta +1 -1
  138. package/site/images/DxMessaging-banner.svg +119 -0
  139. package/site/images/DxMessaging-banner.svg.meta +7 -0
  140. package/site/images.meta +8 -0
  141. package/site/index.html +2 -2
  142. package/site/integrations/index.html +2 -2
  143. package/site/integrations/reflex/index.html +2 -2
  144. package/site/integrations/vcontainer/index.html +2 -2
  145. package/site/integrations/zenject/index.html +2 -2
  146. package/site/integrations.meta +1 -1
  147. package/site/javascripts/csharp-highlight.js.meta +7 -7
  148. package/site/javascripts/mermaid-config.js +4 -1
  149. package/site/javascripts/mermaid-config.js.meta +1 -1
  150. package/site/javascripts.meta +1 -1
  151. package/site/reference/compatibility/index.html +1 -1
  152. package/site/reference/faq/index.html +1 -1
  153. package/site/reference/glossary/index.html +2 -2
  154. package/site/reference/helpers/index.html +15 -15
  155. package/site/reference/quick-reference/index.html +3 -3
  156. package/site/reference/reference/index.html +37 -37
  157. package/site/reference/troubleshooting/index.html +1 -1
  158. package/site/reference.meta +1 -1
  159. package/site/search/search_index.json +1 -1
  160. package/site/sitemap.xml +46 -38
  161. package/site/sitemap.xml.gz +0 -0
  162. package/site/stylesheets/extra.css.meta +1 -1
  163. package/site/stylesheets.meta +1 -1
@@ -0,0 +1,348 @@
1
+ ---
2
+ title: "Test Production Code Directly"
3
+ id: "test-production-code"
4
+ category: "testing"
5
+ version: "1.0.0"
6
+ created: "2026-01-30"
7
+ updated: "2026-01-30"
8
+
9
+ source:
10
+ repository: "wallstop/DxMessaging"
11
+ files:
12
+ - path: "scripts/__tests__/"
13
+ - path: "scripts/"
14
+ url: "https://github.com/wallstop/DxMessaging"
15
+
16
+ tags:
17
+ - "testing"
18
+ - "anti-patterns"
19
+ - "code-quality"
20
+ - "javascript"
21
+ - "maintainability"
22
+ - "testability"
23
+ - "best-practices"
24
+
25
+ complexity:
26
+ level: "intermediate"
27
+ reasoning: "Requires understanding of test design principles and code architecture"
28
+
29
+ impact:
30
+ performance:
31
+ rating: "none"
32
+ details: "Testing patterns only; no runtime performance impact"
33
+ maintainability:
34
+ rating: "critical"
35
+ details: "Prevents tests from diverging from production behavior"
36
+ testability:
37
+ rating: "critical"
38
+ details: "Ensures tests actually verify production code correctness"
39
+
40
+ prerequisites:
41
+ - "Understanding of module exports and imports"
42
+ - "Familiarity with test isolation principles"
43
+
44
+ dependencies:
45
+ packages: []
46
+ skills:
47
+ - "comprehensive-test-coverage"
48
+ - "script-test-coverage"
49
+
50
+ applies_to:
51
+ languages:
52
+ - "JavaScript"
53
+ - "TypeScript"
54
+ - "C#"
55
+ frameworks:
56
+ - "Jest"
57
+ - "Node.js"
58
+ - "NUnit"
59
+ versions:
60
+ node: ">=18.0"
61
+ jest: ">=29.0"
62
+
63
+ aliases:
64
+ - "Test real code"
65
+ - "Don't re-implement in tests"
66
+ - "Test production directly"
67
+ - "Avoid test duplication"
68
+
69
+ related:
70
+ - "comprehensive-test-coverage"
71
+ - "script-test-coverage"
72
+ - "test-code-quality"
73
+
74
+ status: "stable"
75
+ ---
76
+
77
+ # Test Production Code Directly
78
+
79
+ > **One-line summary**: Tests must import and use production code, never re-implement production logic locally.
80
+
81
+ ## Overview
82
+
83
+ A common anti-pattern in testing is re-implementing production validation logic inside test files. When tests maintain their own copies of validation rules, they can pass even when production code regresses. This skill documents how to structure code for testability and avoid this dangerous pattern.
84
+
85
+ ## Problem Statement
86
+
87
+ ### The Anti-Pattern
88
+
89
+ Tests that re-implement production logic create a false sense of security:
90
+
91
+ ```javascript
92
+ // PRODUCTION: scripts/validate-data.js
93
+ function validateRecord(record) {
94
+ const errors = [];
95
+ if (!record.name || record.name.length < 3) {
96
+ errors.push("Name must be at least 3 characters");
97
+ }
98
+ if (!record.email || !record.email.includes("@")) {
99
+ errors.push("Email must be valid");
100
+ }
101
+ return errors;
102
+ }
103
+
104
+ // TEST: scripts/__tests__/validate-data.test.js
105
+ // WRONG: Re-implementing validation locally
106
+ function localValidateRecord(record) {
107
+ const errors = [];
108
+ if (!record.name || record.name.length < 3) {
109
+ // Duplicated logic!
110
+ errors.push("Name must be at least 3 characters");
111
+ }
112
+ if (!record.email || !record.email.includes("@")) {
113
+ // Duplicated logic!
114
+ errors.push("Email must be valid");
115
+ }
116
+ return errors;
117
+ }
118
+
119
+ test("should validate record correctly", () => {
120
+ const result = localValidateRecord({ name: "Jo", email: "bad" });
121
+ expect(result).toHaveLength(2); // Tests pass, but production untested!
122
+ });
123
+ ```
124
+
125
+ ### Why This Is Dangerous
126
+
127
+ 1. **Production regressions go undetected**: If someone changes `< 3` to `< 2` in production, tests still pass because they use the local copy.
128
+ 1. **Maintenance burden doubles**: Every production change requires updating test copies.
129
+ 1. **Divergence over time**: Local test copies gradually drift from production reality.
130
+ 1. **False confidence**: 100% test coverage means nothing if tests don't exercise production code.
131
+ 1. **Bug duplication**: If the same bug exists in both copies, tests won't catch it.
132
+
133
+ ## Solution
134
+
135
+ ### Structure Production Code for Testability
136
+
137
+ Export validation functions and helper logic so tests can import them:
138
+
139
+ ```javascript
140
+ // PRODUCTION: scripts/validate-data.js
141
+ /**
142
+ * Validates a record and returns any errors.
143
+ * @param {Object} record - The record to validate
144
+ * @returns {string[]} Array of error messages
145
+ */
146
+ function validateRecord(record) {
147
+ const errors = [];
148
+ if (!isValidName(record.name)) {
149
+ errors.push("Name must be at least 3 characters");
150
+ }
151
+ if (!isValidEmail(record.email)) {
152
+ errors.push("Email must be valid");
153
+ }
154
+ return errors;
155
+ }
156
+
157
+ /**
158
+ * Validates name length.
159
+ * @param {string} name - The name to validate
160
+ * @returns {boolean} True if valid
161
+ */
162
+ function isValidName(name) {
163
+ return name && name.length >= 3;
164
+ }
165
+
166
+ /**
167
+ * Validates email format.
168
+ * @param {string} email - The email to validate
169
+ * @returns {boolean} True if valid
170
+ */
171
+ function isValidEmail(email) {
172
+ return email && email.includes("@");
173
+ }
174
+
175
+ // Export everything tests need
176
+ module.exports = {
177
+ validateRecord,
178
+ isValidName,
179
+ isValidEmail
180
+ };
181
+ ```
182
+
183
+ ### Test Production Code Directly
184
+
185
+ Import and test the actual production functions:
186
+
187
+ ```javascript
188
+ // TEST: scripts/__tests__/validate-data.test.js
189
+ // CORRECT: Import production code
190
+ const { validateRecord, isValidName, isValidEmail } = require("../validate-data");
191
+
192
+ describe("validateRecord", () => {
193
+ test("should return no errors for valid record", () => {
194
+ const result = validateRecord({ name: "John", email: "john@example.com" });
195
+ expect(result).toHaveLength(0);
196
+ });
197
+
198
+ test("should return error for short name", () => {
199
+ const result = validateRecord({ name: "Jo", email: "john@example.com" });
200
+ expect(result).toContain("Name must be at least 3 characters");
201
+ });
202
+
203
+ test("should return error for invalid email", () => {
204
+ const result = validateRecord({ name: "John", email: "invalid" });
205
+ expect(result).toContain("Email must be valid");
206
+ });
207
+ });
208
+
209
+ describe("isValidName", () => {
210
+ test("should accept names with 3 or more characters", () => {
211
+ expect(isValidName("Joe")).toBe(true);
212
+ expect(isValidName("John")).toBe(true);
213
+ });
214
+
215
+ test("should reject names with fewer than 3 characters", () => {
216
+ expect(isValidName("Jo")).toBe(false);
217
+ expect(isValidName("J")).toBe(false);
218
+ });
219
+
220
+ test("should reject null and undefined", () => {
221
+ expect(isValidName(null)).toBe(false);
222
+ expect(isValidName(undefined)).toBe(false);
223
+ });
224
+ });
225
+ ```
226
+
227
+ ## When Local Test Helpers Are Acceptable
228
+
229
+ Not all code in test files is duplication. These are legitimate uses:
230
+
231
+ ### Thin Wrappers for Test Convenience
232
+
233
+ ```javascript
234
+ // ACCEPTABLE: Thin wrapper that delegates to production
235
+ function validateAndExpectErrors(record, expectedCount) {
236
+ const result = validateRecord(record); // Calls production!
237
+ expect(result).toHaveLength(expectedCount);
238
+ return result;
239
+ }
240
+ ```
241
+
242
+ ### Test-Only Utilities
243
+
244
+ ```javascript
245
+ // ACCEPTABLE: Test data generators
246
+ function createValidRecord(overrides = {}) {
247
+ return {
248
+ name: "Default Name",
249
+ email: "default@example.com",
250
+ ...overrides
251
+ };
252
+ }
253
+
254
+ // ACCEPTABLE: Custom matchers
255
+ function expectValidationError(result, expectedMessage) {
256
+ expect(result.some((msg) => msg.includes(expectedMessage))).toBe(true);
257
+ }
258
+
259
+ // ACCEPTABLE: Factories for test data
260
+ const TestRecords = {
261
+ valid: { name: "John Doe", email: "john@example.com" },
262
+ invalidName: { name: "X", email: "john@example.com" }
263
+ };
264
+ ```
265
+
266
+ ## Handling Unavoidable Duplication
267
+
268
+ Some situations require maintaining parallel implementations. Use SYNC notes to keep them aligned.
269
+
270
+ ### PowerShell Scripts with JavaScript Tests
271
+
272
+ When the production script is PowerShell but tests are JavaScript:
273
+
274
+ ```powershell
275
+ # PRODUCTION: scripts/validate-data.ps1
276
+ # SYNC: Keep validation logic in sync with validate-data.test.js validateRecord()
277
+ function Test-Record {
278
+ param([hashtable]$Record)
279
+ $errors = @()
280
+ if (-not $Record.name -or $Record.name.Length -lt 3) {
281
+ $errors += "Name must be at least 3 characters"
282
+ }
283
+ return $errors
284
+ }
285
+ ```
286
+
287
+ ```javascript
288
+ // TEST: scripts/__tests__/validate-data.test.js
289
+ // SYNC: Keep validation logic in sync with validate-data.ps1 Test-Record
290
+ function validateRecord(record) {
291
+ const errors = [];
292
+ if (!record.name || record.name.length < 3) {
293
+ errors += "Name must be at least 3 characters";
294
+ }
295
+ return errors;
296
+ }
297
+ ```
298
+
299
+ ### Browser-Only Code
300
+
301
+ When code runs only in browsers and cannot be tested in Node.js:
302
+
303
+ ```javascript
304
+ // PRODUCTION: browser-only.js (uses DOM APIs)
305
+ // SYNC: Core logic duplicated for testing in browser-only.test.js
306
+
307
+ // TEST: browser-only.test.js
308
+ // SYNC: Keep validation logic in sync with browser-only.js computeLayout()
309
+ // This is a test-only implementation because production uses DOM APIs
310
+ ```
311
+
312
+ ## SYNC Note Best Practices
313
+
314
+ When duplication is unavoidable, follow these SYNC note guidelines:
315
+
316
+ 1. **Always bidirectional**: Both files must reference each other
317
+ 1. **Never use line numbers**: Reference function names, not line numbers that change
318
+ 1. **Use descriptive identifiers**: `SYNC: Keep in sync with validate.js isValidEmail()` not `SYNC: Keep in sync with validate.js line 42`
319
+ 1. **Verify references exist**: Confirm the referenced function exists before adding the note
320
+ 1. **Update both on changes**: When modifying synced code, update both locations
321
+
322
+ ## Red Flags to Watch For
323
+
324
+ These patterns suggest tests may not be exercising production code:
325
+
326
+ | Red Flag | Why It's Suspicious |
327
+ | ------------------------------------------------------ | ----------------------------- |
328
+ | Test file defines validation constants | Should import from production |
329
+ | Test file has utility functions that mirror production | Should import instead |
330
+ | Test never imports the module it's supposedly testing | Tests itself, not production |
331
+ | Test file size rivals production file size | Too much duplicated logic |
332
+ | Same bug exists in production and tests | Copied code with copied bugs |
333
+
334
+ ## Verification Checklist
335
+
336
+ Before merging, verify tests exercise production code:
337
+
338
+ 1. **Check imports**: Does the test file import from the production module?
339
+ 1. **Check coverage**: Does production file show coverage when tests run?
340
+ 1. **Mutation test**: Change production code slightly - do tests fail?
341
+ 1. **Review test functions**: Are they calling production code or local copies?
342
+ 1. **Search for duplication**: Do test and production files have similar function bodies?
343
+
344
+ ## See Also
345
+
346
+ - [Comprehensive Test Coverage skill](comprehensive-test-coverage.md) - What to test
347
+ - [Script Test Coverage skill](script-test-coverage.md) - Testing scripts specifically
348
+ - [Test Code Quality skill](test-code-quality.md) - Test documentation accuracy
package/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.1.7]
11
+
12
+ ### Changed
13
+
14
+ - Improved README with prominent Mental Model section
15
+ - Added Mermaid diagrams and decision flowchart for choosing message types
16
+ - Added Common Mistakes callout with troubleshooting link
17
+ - Updated performance comparison table with accurate benchmark range (10-17M ops/sec)
18
+
19
+ ### Fixed
20
+
21
+ - Regenerated corrupted meta files in `scripts/wiki`
22
+
23
+ ## [2.1.6]
24
+
25
+ ### Added
26
+
27
+ - Concepts index page and Mental Model documentation for understanding DxMessaging's design principles
28
+
29
+ ### Fixed
30
+
31
+ - Orphaned documentation pages in Concepts section now included in mkdocs.yml navigation
32
+ - Burst compiler assembly resolution errors when using DxMessaging as a package on disk and building for player platforms. Benchmarks and integration test assembly definitions now specify Editor-only platform to prevent Burst from attempting to resolve these assemblies during player builds.
33
+
10
34
  ## [2.1.5]
11
35
 
12
36
  ### Added
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # DxMessaging for Unity
2
2
 
3
3
  <p align="center">
4
- <img src="docs/images/DxMessaging-banner.svg" alt="DxMessaging Banner" width="800"/>
4
+ <img src="docs/images/DxMessaging-banner.svg" alt="DxMessaging - Type-safe messaging system for Unity" width="800"/>
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -31,9 +31,11 @@ Need install instructions? Try [OpenUPM](https://openupm.com/packages/com.wallst
31
31
  ## Table of Contents
32
32
 
33
33
  - [30-Second Elevator Pitch](#30-second-elevator-pitch)
34
+ - [Mental Model: How to Think About DxMessaging](#mental-model-how-to-think-about-dxmessaging)
34
35
  - [Quick Start (5 Minutes)](#quick-start-5-minutes)
36
+ - [Dependency Injection (DI) Compatible](#-dependency-injection-di-compatible)
35
37
  - [Is DxMessaging Right for You?](#is-dxmessaging-right-for-you)
36
- - [Why DxMessaging?](#why-dxmessaging)
38
+ - [Why DxMessaging](#why-dxmessaging)
37
39
  - [Key Features](#key-features)
38
40
  - [The DxMessaging Solution](#the-dxmessaging-solution)
39
41
  - [Real-World Examples](#real-world-examples)
@@ -65,14 +67,114 @@ Need install instructions? Try [OpenUPM](https://openupm.com/packages/com.wallst
65
67
 
66
68
  ##### Three simple message types
67
69
 
68
- 1. **Untargeted** - "Everyone listen!" (pause game, settings changed)
69
- 1. **Targeted** - "Tell Player to heal" (commands to specific entities)
70
- 1. **Broadcast** - "I took damage" (things that happen to _you_ that others can observe)
70
+ 1. **Untargeted** - Global announcements
71
+ 1. **Targeted** - Commands to specific entities
72
+ 1. **Broadcast** - Observable facts from a source
73
+
74
+ See [Mental Model](#mental-model-how-to-think-about-dxmessaging) for how to choose the right type.
71
75
 
72
76
  **One line:** It's a type-safe messaging system with automatic lifecycle management and built-in inspection tools.
73
77
 
74
78
  ---
75
79
 
80
+ ## Mental Model: How to Think About DxMessaging
81
+
82
+ ### The Core Idea
83
+
84
+ DxMessaging is built around one principle: **it gets out of your way**.
85
+
86
+ You have data. You need to pass it around. That's the problem. DxMessaging provides fast, simple primitives as building blocks. You model changes as message types with optional context, using game primitives (GameObjects, components) as that context.
87
+
88
+ **You don't build your game INTO the messaging system.** It's opt-in and optional—a tool you reach for when it helps.
89
+
90
+ ### The Three Message Types: Real-World Analogies
91
+
92
+ > 💡 _Diagrams below require Mermaid support. If they don't render, try viewing this file directly on [GitHub](https://github.com/wallstop/DxMessaging)._
93
+
94
+ Each message type maps to a real-world communication pattern:
95
+
96
+ #### 1. Untargeted = PA System 📢
97
+
98
+ ```mermaid
99
+ flowchart LR
100
+ S[Someone] -->|announces| PA[📢 PA System]
101
+ PA --> L1[Listener A]
102
+ PA --> L2[Listener B]
103
+ PA --> L3[Listener C]
104
+ ```
105
+
106
+ Announcements with no specific recipient. Everyone who cares can hear it.
107
+
108
+ **Examples:** "The game is paused", "Settings changed", "Scene finished loading"
109
+
110
+ #### 2. Targeted = Addressed Letter 📬
111
+
112
+ ```mermaid
113
+ flowchart LR
114
+ S[Sender] -->|"To: Player"| Letter[📬 Message Bus]
115
+ Letter --> Player[Player receives]
116
+ Other1[Enemy A] -.->|ignores| Letter
117
+ Other2[Enemy B] -.->|ignores| Letter
118
+ ```
119
+
120
+ Commands to a specific recipient. Only that entity receives them.
121
+
122
+ **Examples:** "Player, heal for 10 HP", "Door #7, open", "This enemy, take 25 damage"
123
+
124
+ #### 3. Broadcast = Radio Station 📻
125
+
126
+ ```mermaid
127
+ flowchart LR
128
+ Source[Enemy] -->|"I took damage!"| Radio[📻 Message Bus]
129
+ Radio --> L1[Damage Numbers UI]
130
+ Radio --> L2[Achievement Tracker]
131
+ Radio --> L3[Analytics]
132
+ Radio --> L4[Combat Log]
133
+ ```
134
+
135
+ Facts emitted by a specific source. No intended recipient—just an origin. Anyone can tune in.
136
+
137
+ **Examples:** "This enemy took 25 damage", "The player picked up item X", "This chest opened"
138
+
139
+ ### Choosing the Right Message Type
140
+
141
+ ```mermaid
142
+ flowchart TD
143
+ Start([I need to send a message])
144
+ Start --> Q1{Does it matter<br/>WHO sent it?}
145
+
146
+ Q1 -->|No| Q2{Does it matter<br/>WHO receives it?}
147
+ Q2 -->|No| Untargeted[Use UNTARGETED<br/>Global announcement]
148
+ Q2 -->|Yes| Targeted[Use TARGETED<br/>Directed command]
149
+
150
+ Q1 -->|Yes| Q3{Am I commanding<br/>someone to act?}
151
+ Q3 -->|Yes| Targeted
152
+ Q3 -->|No| Broadcast[Use BROADCAST<br/>Observable fact]
153
+ ```
154
+
155
+ | Question | Untargeted | Targeted | Broadcast |
156
+ | ----------------------------------- | :--------: | :------: | :-------: |
157
+ | Has a specific sender that matters? | ❌ | ❌ | ✅ |
158
+ | Has a specific recipient? | ❌ | ✅ | ❌ |
159
+ | Is it a command? | ❌ | ✅ | ❌ |
160
+ | Is it an observable fact? | Maybe | ❌ | ✅ |
161
+ | Is it a global announcement? | ✅ | ❌ | ❌ |
162
+
163
+ > ⚠️ **Common Mistakes:**
164
+ >
165
+ > - **Forgetting to enable the token** — Messages won't be received. Use `MessageAwareComponent` (auto-enables) or call `Token.Enable()` manually.
166
+ > - **Targeting Component when you meant GameObject** — These are distinct registration paths. Component-targeted messages won't reach GameObject-level handlers.
167
+ > - **Using Broadcast when you need Targeted** — Broadcasts have no recipient, just an origin. Use Targeted when commanding a specific entity.
168
+ > - **Missing `[Dx*Message]` attribute** — The source generator won't process the struct without the marker attribute.
169
+ >
170
+ > 📖 See [Troubleshooting](docs/reference/troubleshooting.md) for solutions to these and other issues.
171
+
172
+ 📖 **Want more depth?** See the full [Mental Model documentation](docs/concepts/mental-model.md) for detailed examples, lifecycle patterns, and edge cases.
173
+
174
+ 📖 **Ready to code?** Jump to [Quick Start](#quick-start-5-minutes) to send your first message!
175
+
176
+ ---
177
+
76
178
  ## Quick Start (5 Minutes)
77
179
 
78
180
  **New to messaging?** Start with the [Visual Guide](docs/getting-started/visual-guide.md) (5 min) for a beginner-friendly introduction!
@@ -85,7 +187,7 @@ Need install instructions? Try [OpenUPM](https://openupm.com/packages/com.wallst
85
187
  openupm add com.wallstop-studios.dxmessaging
86
188
  ```
87
189
 
88
- ##### Or via Git URL
190
+ #### Or via Git URL
89
191
 
90
192
  ```bash
91
193
  # Unity Package Manager > Add package from git URL...
@@ -231,20 +333,10 @@ flowchart TD
231
333
  Q3{Do you need observable, decoupled,<br/>lifecycle-safe messaging?}
232
334
  Q3 -->|YES| A3["✅ Use DxMessaging"]
233
335
  Q3 -->|NO| A4["❌ Keep it simple"]
234
-
235
- style Q1 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
236
- style Q2 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
237
- style Q3 fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
238
- style A1 fill:#f0f0f0,stroke:#666,stroke-width:2px,color:#000
239
- style A2 fill:#f0f0f0,stroke:#666,stroke-width:2px,color:#000
240
- style A3 fill:#95de64,stroke:#237804,stroke-width:3px,color:#000
241
- style A4 fill:#f0f0f0,stroke:#666,stroke-width:2px,color:#000
242
336
  ```
243
337
 
244
338
  **Rule of thumb:** If you're reading this README and thinking "this could address several challenges I'm facing," then DxMessaging may be a good fit. If you're thinking "this seems complicated," start with the [Visual Guide](docs/getting-started/visual-guide.md) or stick with simpler patterns.
245
339
 
246
- **New to messaging?** Start with the [Visual Guide](docs/getting-started/visual-guide.md) (5 min) for a beginner-friendly introduction!
247
-
248
340
  Looking for hard numbers? See OS-specific [Performance Benchmarks](docs/architecture/performance.md).
249
341
 
250
342
  ## Why DxMessaging
@@ -266,7 +358,7 @@ public class GameUI : MonoBehaviour {
266
358
 
267
359
  Months later: "Why is our game using 2GB of RAM after an hour?"
268
360
 
269
- ##### Scenario 2: The Spaghetti Mess
361
+ #### Scenario 2: The Spaghetti Mess
270
362
 
271
363
  ```csharp
272
364
  public class GameUI : MonoBehaviour {
@@ -289,7 +381,7 @@ public class GameUI : MonoBehaviour {
289
381
 
290
382
  **Your UI now depends on many systems.** Refactoring becomes more difficult.
291
383
 
292
- ###### Scenario 3: The Debugging Black Hole
384
+ #### Scenario 3: The Debugging Black Hole
293
385
 
294
386
  Player reports: "My health bar didn't update!"
295
387
 
@@ -326,7 +418,7 @@ public class GameUI : MessageAwareComponent {
326
418
 
327
419
  ###### Automatic lifecycle = leaks are prevented by default
328
420
 
329
- ###### Scenario 2: No More Coupling
421
+ ##### Scenario 2: No More Coupling
330
422
 
331
423
  ```csharp
332
424
  public class GameUI : MessageAwareComponent {
@@ -344,7 +436,7 @@ public class GameUI : MessageAwareComponent {
344
436
 
345
437
  **Your UI is now independent.** Swapping systems no longer requires updating UI references.
346
438
 
347
- ###### Scenario 3: Debugging is Built In
439
+ ##### Scenario 3: Debugging is Built In
348
440
 
349
441
  Open any `MessageAwareComponent` in the Inspector:
350
442
 
@@ -448,9 +540,6 @@ flowchart LR
448
540
  P[Producer] --> I[Interceptors<br/>validate/mutate]
449
541
  I --> H[Handlers<br/>main logic]
450
542
  H --> PP[Post-Processors<br/>analytics/logging]
451
- style I fill:#ffe7ba,stroke:#d48806,stroke-width:2px,color:#000
452
- style H fill:#91d5ff,stroke:#096dd9,stroke-width:2px,color:#000
453
- style PP fill:#b7eb8f,stroke:#389e0d,stroke-width:2px,color:#000
454
543
  ```
455
544
 
456
545
  ### Global Observers: Listen to All Events
@@ -758,7 +847,7 @@ For OS-specific benchmark tables generated by PlayMode tests, see [Performance B
758
847
  | **Interceptors** | ✅ Pipeline | ❌ No | ⚠️ Filters | ❌ No |
759
848
  | **Post-Processing** | ✅ Dedicated | ❌ No | ⚠️ Filters | ❌ No |
760
849
  | **Stream Operators** | ❌ No | ✅ Extensive | ❌ No | ⚠️ With UniRx |
761
- | **Performance** | ✅ Good (14M) | ✅ Good (18M) | ✅ High (97M) | ⚠️ Moderate (2.5M) |
850
+ | **Performance** | ✅ Good (10-17M) | ✅ Good (18M) | ✅ High (97M) | ⚠️ Moderate (2.5M) |
762
851
  | **Dependencies** | ✅ None | ⚠️ UniTask | ✅ None | ⚠️ Zenject |
763
852
 
764
853
  ### Comparison with Traditional Approaches
@@ -12,7 +12,7 @@
12
12
  "UniRx",
13
13
  "UniTask"
14
14
  ],
15
- "includePlatforms": [],
15
+ "includePlatforms": ["Editor"],
16
16
  "excludePlatforms": [],
17
17
  "allowUnsafeCode": false,
18
18
  "overrideReferences": true,
@@ -35,11 +35,6 @@
35
35
  "expression": "0.0.1",
36
36
  "define": "UNIRX_PRESENT"
37
37
  },
38
- {
39
- "name": "com.svermeulen.extenject",
40
- "expression": "0.0.1",
41
- "define": "ZENJECT_PRESENT"
42
- },
43
38
  {
44
39
  "name": "com.svermeulen.extenject",
45
40
  "expression": "0.0.1",
@@ -9,7 +9,7 @@
9
9
  "Reflex",
10
10
  "WallstopStudios.DxMessaging.Reflex"
11
11
  ],
12
- "includePlatforms": [],
12
+ "includePlatforms": ["Editor"],
13
13
  "excludePlatforms": [],
14
14
  "allowUnsafeCode": false,
15
15
  "overrideReferences": true,
@@ -9,7 +9,7 @@
9
9
  "VContainer",
10
10
  "WallstopStudios.DxMessaging.VContainer"
11
11
  ],
12
- "includePlatforms": [],
12
+ "includePlatforms": ["Editor"],
13
13
  "excludePlatforms": [],
14
14
  "allowUnsafeCode": false,
15
15
  "overrideReferences": true,
@@ -9,7 +9,7 @@
9
9
  "Zenject",
10
10
  "WallstopStudios.DxMessaging.Zenject"
11
11
  ],
12
- "includePlatforms": [],
12
+ "includePlatforms": ["Editor"],
13
13
  "excludePlatforms": [],
14
14
  "allowUnsafeCode": false,
15
15
  "overrideReferences": true,