@eventcatalog/core 3.48.4 → 4.0.0-beta.1

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 (109) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +3 -3
  5. package/dist/catalog-to-astro-content-directory.cjs +1 -1
  6. package/dist/chunk-BWN544LB.js +73 -0
  7. package/dist/{chunk-XNVSSL4W.js → chunk-JTZT2SDF.js} +1 -1
  8. package/dist/{chunk-JY4H7A4S.js → chunk-M2ZRT7KQ.js} +1 -1
  9. package/dist/{chunk-SY4XL6OQ.js → chunk-MBVTS76Q.js} +1 -1
  10. package/dist/{chunk-KDLSBQRP.js → chunk-TZTPGQGL.js} +1 -1
  11. package/dist/{chunk-FNDSHOV2.js → chunk-U5OTNDVQ.js} +1 -1
  12. package/dist/constants.cjs +1 -1
  13. package/dist/constants.js +1 -1
  14. package/dist/core-node-modules.cjs +108 -0
  15. package/dist/core-node-modules.d.cts +7 -0
  16. package/dist/core-node-modules.d.ts +7 -0
  17. package/dist/core-node-modules.js +8 -0
  18. package/dist/docs/development/components/components/15-prompt.md +5 -1
  19. package/dist/eventcatalog.cjs +124 -48
  20. package/dist/eventcatalog.js +26 -15
  21. package/dist/generate.cjs +1 -1
  22. package/dist/generate.js +3 -3
  23. package/dist/utils/cli-logger.cjs +1 -1
  24. package/dist/utils/cli-logger.js +2 -2
  25. package/eventcatalog/astro.config.mjs +27 -23
  26. package/eventcatalog/public/icons/languages/java.svg +1 -0
  27. package/eventcatalog/public/icons/payments/stripe.svg +1 -0
  28. package/eventcatalog/src/components/Badge.astro +10 -10
  29. package/eventcatalog/src/components/CopyAsMarkdown.tsx +154 -0
  30. package/eventcatalog/src/components/Grids/DomainGrid.tsx +207 -44
  31. package/eventcatalog/src/components/Grids/MessageGrid.tsx +0 -20
  32. package/eventcatalog/src/components/Grids/SystemGrid.tsx +185 -0
  33. package/eventcatalog/src/components/MDX/ADRTable/ADRTable.astro +99 -0
  34. package/eventcatalog/src/components/MDX/ADRTable/ADRTable.client.tsx +400 -0
  35. package/eventcatalog/src/components/MDX/ContextDiagram/ContextDiagramPortal.tsx +21 -0
  36. package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +1 -0
  37. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +42 -6
  38. package/eventcatalog/src/components/MDX/Prompt/Prompt.astro +62 -9
  39. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +8 -1
  40. package/eventcatalog/src/components/MDX/SystemContextMap/SystemContextMapPortal.tsx +22 -0
  41. package/eventcatalog/src/components/MDX/components.tsx +7 -0
  42. package/eventcatalog/src/components/SchemaExplorer/SchemaExplorer.tsx +1 -1
  43. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +67 -37
  44. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +47 -0
  45. package/eventcatalog/src/components/Tables/Discover/FilterComponents.tsx +10 -2
  46. package/eventcatalog/src/components/Tables/Discover/columns.tsx +115 -27
  47. package/eventcatalog/src/components/Tables/Discover/index.ts +7 -1
  48. package/eventcatalog/src/components/Tables/SystemResources/SystemResourcesTable.tsx +267 -0
  49. package/eventcatalog/src/content.config.ts +96 -8
  50. package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +4 -1
  51. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +12 -13
  52. package/eventcatalog/src/enterprise/custom-documentation/utils/custom-docs.ts +6 -0
  53. package/eventcatalog/src/enterprise/feature.ts +3 -1
  54. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +96 -140
  55. package/eventcatalog/src/layouts/VisualiserLayout.astro +66 -0
  56. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +4 -2
  57. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +2 -0
  58. package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +15 -15
  59. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +1 -0
  60. package/eventcatalog/src/pages/discover/[type]/index.astro +47 -2
  61. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +15 -14
  62. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +13 -14
  63. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -0
  64. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +11 -1
  65. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +81 -26
  66. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +89 -0
  67. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +85 -0
  68. package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +2 -0
  69. package/eventcatalog/src/pages/docs/[type]/[id]/[version].mdx.ts +2 -0
  70. package/eventcatalog/src/pages/docs/[type]/[id]/_index.data.ts +1 -0
  71. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +24 -1
  72. package/eventcatalog/src/pages/docs/users/[id]/index.astro +24 -1
  73. package/eventcatalog/src/pages/index.astro +30 -7
  74. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +1 -0
  75. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/index.astro +1 -1
  76. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version].mermaid.ts +3 -0
  77. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/_index.data.ts +94 -0
  78. package/eventcatalog/src/pages/visualiser/domains/[id]/[version]/systems-context/index.astro +51 -0
  79. package/eventcatalog/src/pages/visualiser/system-context-map/index.astro +41 -0
  80. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/_index.data.ts +91 -0
  81. package/eventcatalog/src/pages/visualiser/systems/[id]/[version]/context/index.astro +51 -0
  82. package/eventcatalog/src/stores/sidebar-store/builders/adr.ts +3 -1
  83. package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +3 -1
  84. package/eventcatalog/src/stores/sidebar-store/builders/container.ts +1 -1
  85. package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +1 -1
  86. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +127 -49
  87. package/eventcatalog/src/stores/sidebar-store/builders/message.ts +1 -1
  88. package/eventcatalog/src/stores/sidebar-store/builders/service.ts +4 -2
  89. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +2 -1
  90. package/eventcatalog/src/stores/sidebar-store/builders/system.ts +166 -0
  91. package/eventcatalog/src/stores/sidebar-store/state.ts +99 -6
  92. package/eventcatalog/src/types/index.ts +2 -0
  93. package/eventcatalog/src/utils/collection-colors.ts +6 -1
  94. package/eventcatalog/src/utils/collections/domains.ts +35 -0
  95. package/eventcatalog/src/utils/collections/icons.ts +4 -5
  96. package/eventcatalog/src/utils/collections/systems.ts +110 -0
  97. package/eventcatalog/src/utils/collections/teams.ts +15 -10
  98. package/eventcatalog/src/utils/collections/users.ts +7 -4
  99. package/eventcatalog/src/utils/collections/util.ts +2 -0
  100. package/eventcatalog/src/utils/icon-map.ts +20 -0
  101. package/eventcatalog/src/utils/icon.ts +11 -0
  102. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +12 -2
  103. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +0 -170
  104. package/eventcatalog/src/utils/node-graphs/system-context-node-graph.ts +400 -0
  105. package/eventcatalog/src/utils/node-graphs/systems-node-graph.ts +208 -0
  106. package/eventcatalog/src/utils/node-graphs/utils/utils.ts +12 -0
  107. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
  108. package/package.json +17 -14
  109. package/eventcatalog/src/pages/visualiser/context-map/index.astro +0 -30
@@ -0,0 +1,51 @@
1
+ ---
2
+ import NodeGraph from '@components/MDX/NodeGraph/NodeGraph.astro';
3
+ import VisualiserLayout from '@layouts/VisualiserLayout.astro';
4
+ import { ClientRouter } from 'astro:transitions';
5
+
6
+ import { Page } from './_index.data';
7
+
8
+ export const prerender = Page.prerender;
9
+ export const getStaticPaths = Page.getStaticPaths;
10
+
11
+ // Get data
12
+ const props = await Page.getData(Astro);
13
+
14
+ const {
15
+ data: { id },
16
+ } = props;
17
+ ---
18
+
19
+ <VisualiserLayout title={`Visualiser | ${props.data.name} - System Context`} description={props.data.summary}>
20
+ <div class="p-2">
21
+ <div
22
+ class="h-[calc(100vh-5rem)] w-full relative border border-[rgb(var(--ec-page-border))]"
23
+ id={`${id}-portal`}
24
+ transition:name="visualiser-graph"
25
+ >
26
+ </div>
27
+ <NodeGraph
28
+ id={id}
29
+ collection="systems-context"
30
+ title={`${props.data.name} (v${props.data.version}) - System Context`}
31
+ mode="full"
32
+ linkTo="visualiser"
33
+ version={props.data.version}
34
+ linksToVisualiser={false}
35
+ />
36
+ </div>
37
+ <ClientRouter />
38
+ </VisualiserLayout>
39
+
40
+ <script define:vars={{ id }}>
41
+ document.addEventListener('DOMContentLoaded', () => {
42
+ const urlSearchParams = new URLSearchParams(window.location.search);
43
+ const params = Object.fromEntries(urlSearchParams.entries());
44
+ const embeded = params.embed === 'true' ? true : false;
45
+ const viewport = document.getElementById(`${id}-portal`);
46
+
47
+ if (embeded) {
48
+ viewport.style.height = 'calc(100vh - 30px)';
49
+ }
50
+ });
51
+ </script>
@@ -27,6 +27,7 @@ const firstClassResourceCollections = [
27
27
  'flows',
28
28
  'channels',
29
29
  'domains',
30
+ 'systems',
30
31
  'users',
31
32
  'teams',
32
33
  'containers',
@@ -50,6 +51,7 @@ const getResourcesForCollection = (collection: string, context: ResourceGroupCon
50
51
  flows: context.flows,
51
52
  channels: context.channels || [],
52
53
  domains: context.domains,
54
+ systems: context.systems || [],
53
55
  users: context.users || [],
54
56
  teams: context.teams || [],
55
57
  containers: context.containers,
@@ -113,7 +115,7 @@ export const buildAdrNode = (adr: Adr, owners: any[], decisionMakers: any[], con
113
115
  title: adr.data.name,
114
116
  badge: 'Decision record',
115
117
  summary: adr.data.summary,
116
- icon: 'BookText',
118
+ icon: 'ClipboardList',
117
119
  pages: [
118
120
  buildQuickReferenceSection(
119
121
  [
@@ -129,7 +129,9 @@ export const buildAgentNode = (
129
129
  },
130
130
  hasFlows && {
131
131
  type: 'group',
132
- title: 'Flows',
132
+ // If the agent declares its own flows it owns them ("Flows"); otherwise it is
133
+ // only referenced as a step in someone else's flow ("Appears in flows").
134
+ title: agentFlows.length > 0 ? 'Flows' : 'Appears in flows',
133
135
  icon: 'Waypoints',
134
136
  pages: agentFlowRefs,
135
137
  },
@@ -112,7 +112,7 @@ export const buildContainerNode = (
112
112
  },
113
113
  renderFlows && {
114
114
  type: 'group',
115
- title: 'Flows',
115
+ title: 'Appears in flows',
116
116
  icon: 'Waypoints',
117
117
  pages: flowRefs,
118
118
  visible: flowRefs.length > 0,
@@ -143,7 +143,7 @@ export const buildDataProductNode = (
143
143
  },
144
144
  renderFlows && {
145
145
  type: 'group',
146
- title: 'Flows',
146
+ title: 'Appears in flows',
147
147
  icon: 'Waypoints',
148
148
  pages: flowRefs,
149
149
  visible: flowRefs.length > 0,
@@ -14,14 +14,17 @@ import {
14
14
  import { isVisualiserEnabled, isChangelogEnabled } from '@utils/feature';
15
15
  import { pluralizeMessageType } from '@utils/collections/messages';
16
16
  import { getSpecificationsForDomain, hasUbiquitousLanguageTermsWithSubdomainsInCollection } from '@utils/collections/domains';
17
- import { iconFieldsForResource } from '@utils/icon';
17
+ import { customIconFieldsForResource } from '@utils/icon';
18
+
19
+ // Sort resolved collection entries A-Z by their display name (falling back to id).
20
+ const byResourceName = (a: any, b: any) => (a.data?.name || a.data?.id || '').localeCompare(b.data?.name || b.data?.id || '');
18
21
 
19
22
  export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[], context: ResourceGroupContext): NavNode => {
20
23
  const agentsInDomain = domain.data.agents || [];
21
24
  const renderAgents = agentsInDomain.length > 0 && shouldRenderSideBarSection(domain, 'agents');
22
25
 
23
26
  const allServicesInDomain = domain.data.services || [];
24
- const servicesInDomain = allServicesInDomain.filter((service) => !(service as any).data?.externalSystem);
27
+ const servicesInDomain = allServicesInDomain.filter((service) => !(service as any).data?.externalSystem).sort(byResourceName);
25
28
  const externalSystemsInDomain = allServicesInDomain.filter((service) => (service as any).data?.externalSystem);
26
29
  const renderServices = servicesInDomain.length > 0 && shouldRenderSideBarSection(domain, 'services');
27
30
  const renderExternalSystems = externalSystemsInDomain.length > 0 && shouldRenderSideBarSection(domain, 'services');
@@ -29,13 +32,25 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
29
32
  const dataProductsInDomain = domain.data['data-products'] || [];
30
33
  const renderDataProducts = dataProductsInDomain.length > 0 && shouldRenderSideBarSection(domain, 'data-products');
31
34
 
35
+ const systemsInDomain = domain.data.systems || [];
36
+ const renderSystems = systemsInDomain.length > 0 && shouldRenderSideBarSection(domain, 'systems');
37
+
38
+ // The domain's System Diagram only has something to show when at least one of
39
+ // its systems takes part in a context graph (declares relationships or actors). This
40
+ // mirrors the guard that generates the visualiser page, so we never link to a page
41
+ // that wasn't generated.
42
+ const hasSystemContext = systemsInDomain.some((system: any) => {
43
+ const data = system?.data || system;
44
+ return (data?.relationships || []).length > 0 || (data?.actors || []).length > 0;
45
+ });
46
+
32
47
  const subDomains = domain.data.domains || [];
33
48
  const renderSubDomains = subDomains.length > 0 && shouldRenderSideBarSection(domain, 'subdomains');
34
49
 
35
- const entitiesInDomain = domain.data.entities || [];
50
+ const entitiesInDomain = [...(domain.data.entities || [])].sort(byResourceName);
36
51
  const renderEntities = entitiesInDomain.length > 0 && shouldRenderSideBarSection(domain, 'entities');
37
52
 
38
- const domainFlows = domain.data.flows || [];
53
+ const domainFlows = [...(domain.data.flows || [])].sort(byResourceName);
39
54
  const hasFlows = domainFlows.length > 0;
40
55
 
41
56
  const resourceGroups = domain.data.resourceGroups || [];
@@ -62,8 +77,29 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
62
77
  // Domain-level messages (sends/receives)
63
78
  const sendsMessages = domain.data.sends || [];
64
79
  const receivesMessages = domain.data.receives || [];
80
+ const sortedSendsMessages = [...sendsMessages].sort(byResourceName);
81
+ const sortedReceivesMessages = [...receivesMessages].sort(byResourceName);
65
82
  const renderMessages = shouldRenderSideBarSection(domain, 'messages');
66
83
 
84
+ // The Resources page/link only makes sense when the domain actually has resources
85
+ // attached (services, flows, entities or its own messages). Mirrors what the page renders.
86
+ const hasResources =
87
+ servicesInDomain.length > 0 ||
88
+ domainFlows.length > 0 ||
89
+ entitiesInDomain.length > 0 ||
90
+ sendsMessages.length > 0 ||
91
+ receivesMessages.length > 0;
92
+
93
+ // The Resource Diagram renders the domain's services, agents, data products and
94
+ // subdomains (see domains-node-graph). Only link to it when there's something to
95
+ // draw — otherwise the visualiser page is empty.
96
+ const hasResourceDiagram =
97
+ servicesInDomain.length > 0 ||
98
+ externalSystemsInDomain.length > 0 ||
99
+ agentsInDomain.length > 0 ||
100
+ dataProductsInDomain.length > 0 ||
101
+ subDomains.length > 0;
102
+
67
103
  // Diagrams
68
104
  const domainDiagrams = domain.data.diagrams || [];
69
105
  const diagramNavItems = buildDiagramNavItems(domainDiagrams, context.diagrams);
@@ -82,11 +118,18 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
82
118
  title: domain.data.name,
83
119
  badge: 'Domain',
84
120
  summary: domain.data.summary,
85
- ...iconFieldsForResource(domain.data, 'Boxes'),
121
+ // Domains use a custom icon when defined, otherwise none — the surrounding
122
+ // 'Domains' section header (and Domain badge) already convey the type, so the
123
+ // default Boxes glyph on every item is redundant.
124
+ ...customIconFieldsForResource(domain.data),
86
125
  pages: [
87
126
  buildQuickReferenceSection(
88
127
  [
89
128
  { title: 'Overview', href: buildUrl(`/docs/domains/${domain.data.id}/${domain.data.version}`) },
129
+ hasResources && {
130
+ title: 'Domain Resources',
131
+ href: buildUrl(`/docs/domains/${domain.data.id}/${domain.data.version}/resources`),
132
+ },
90
133
  renderUbiquitousLanguage && {
91
134
  title: 'Ubiquitous Language',
92
135
  href: buildUrl(`/docs/domains/${domain.data.id}/language`),
@@ -109,15 +152,23 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
109
152
  title: 'Overview',
110
153
  href: buildUrl(`/architecture/domains/${domain.data.id}/${domain.data.version}`),
111
154
  },
112
- renderVisualiser && {
113
- type: 'item',
114
- title: 'Map',
115
- href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}`),
116
- },
155
+ renderSystems &&
156
+ renderVisualiser &&
157
+ hasSystemContext && {
158
+ type: 'item',
159
+ title: 'System Diagram',
160
+ href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}/systems-context`),
161
+ },
162
+ renderVisualiser &&
163
+ hasResourceDiagram && {
164
+ type: 'item',
165
+ title: 'Resource Diagram',
166
+ href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}`),
167
+ },
117
168
  renderEntities &&
118
169
  renderVisualiser && {
119
170
  type: 'item',
120
- title: 'Entity Map',
171
+ title: 'Entity Diagram',
121
172
  href: buildUrl(`/visualiser/domains/${domain.data.id}/${domain.data.version}/entity-map`),
122
173
  },
123
174
  ].filter(Boolean) as ChildRef[],
@@ -159,36 +210,81 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
159
210
  })),
160
211
  ],
161
212
  },
213
+ renderSystems && {
214
+ type: 'group',
215
+ title: 'Systems',
216
+ icon: 'Group',
217
+ pages: systemsInDomain.map((system) => `system:${(system as any).data.id}:${(system as any).data.version}`),
218
+ },
162
219
  renderSubDomains && {
163
220
  type: 'group',
164
221
  title: 'Subdomains',
165
222
  icon: 'Boxes',
166
223
  pages: subDomains.map((domain) => `domain:${(domain as any).data.id}:${(domain as any).data.version}`),
167
224
  },
168
- hasFlows && {
225
+ (renderServices ||
226
+ hasFlows ||
227
+ renderEntities ||
228
+ (renderMessages && (sendsMessages.length > 0 || receivesMessages.length > 0))) && {
169
229
  type: 'group',
170
- title: 'Flows',
171
- icon: 'Waypoints',
172
- pages: domainFlows.map((flow) => `flow:${(flow as any).data.id}:${(flow as any).data.version}`),
173
- },
174
- renderEntities && {
175
- type: 'group',
176
- title: 'Entities',
177
- icon: 'Box',
178
- pages: entitiesInDomain.map((entity) => ({
179
- type: 'item',
180
- title: (entity as any).data?.name || (entity as any).data.id,
181
- href: buildUrl(`/docs/entities/${(entity as any).data.id}/${(entity as any).data.version}`),
182
- })),
230
+ title: 'Resources',
231
+ icon: 'Boxes',
232
+ // Resource type subsections are ordered A-Z by their title, and the
233
+ // resources within each subsection are ordered A-Z by name (sorted above).
234
+ pages: (
235
+ [
236
+ renderServices && {
237
+ type: 'group',
238
+ title: 'Services',
239
+ subtle: true,
240
+ icon: 'Server',
241
+ pages: servicesInDomain.map((service) => `service:${(service as any).data.id}:${(service as any).data.version}`),
242
+ },
243
+ hasFlows && {
244
+ type: 'group',
245
+ title: 'Flows',
246
+ subtle: true,
247
+ icon: 'Waypoints',
248
+ pages: domainFlows.map((flow) => `flow:${(flow as any).data.id}:${(flow as any).data.version}`),
249
+ },
250
+ renderEntities && {
251
+ type: 'group',
252
+ title: 'Entities',
253
+ subtle: true,
254
+ icon: 'Box',
255
+ pages: entitiesInDomain.map((entity) => ({
256
+ type: 'item',
257
+ title: (entity as any).data?.name || (entity as any).data.id,
258
+ href: buildUrl(`/docs/entities/${(entity as any).data.id}/${(entity as any).data.version}`),
259
+ })),
260
+ },
261
+ renderMessages &&
262
+ sendsMessages.length > 0 && {
263
+ type: 'group',
264
+ title: 'Domain Events',
265
+ subtle: true,
266
+ icon: 'Mail',
267
+ pages: sortedSendsMessages.map(
268
+ (message) =>
269
+ `${pluralizeMessageType(message as any)}:${(message as any).data.id}:${(message as any).data.version}`
270
+ ),
271
+ },
272
+ renderMessages &&
273
+ receivesMessages.length > 0 && {
274
+ type: 'group',
275
+ title: 'External Events',
276
+ subtle: true,
277
+ icon: 'Mail',
278
+ pages: sortedReceivesMessages.map(
279
+ (receive) =>
280
+ `${pluralizeMessageType(receive as any)}:${(receive as any).data.id}:${(receive as any).data.version}`
281
+ ),
282
+ },
283
+ ].filter(Boolean) as NavNode[]
284
+ ).sort((a, b) => a.title.localeCompare(b.title)) as ChildRef[],
183
285
  },
184
286
 
185
287
  ...(hasResourceGroups ? buildResourceGroupSections(resourceGroups, context) : []),
186
- renderServices && {
187
- type: 'group',
188
- title: 'Services In Domain',
189
- icon: 'Server',
190
- pages: servicesInDomain.map((service) => `service:${(service as any).data.id}:${(service as any).data.version}`),
191
- },
192
288
  renderAgents && {
193
289
  type: 'group',
194
290
  title: 'Agents In Domain',
@@ -209,24 +305,6 @@ export const buildDomainNode = (domain: CollectionEntry<'domains'>, owners: any[
209
305
  (dataProduct) => `data-product:${(dataProduct as any).data.id}:${(dataProduct as any).data.version}`
210
306
  ),
211
307
  },
212
- sendsMessages.length > 0 &&
213
- renderMessages && {
214
- type: 'group',
215
- title: 'Domain Events',
216
- icon: 'Mail',
217
- pages: sendsMessages.map(
218
- (message) => `${pluralizeMessageType(message as any)}:${(message as any).data.id}:${(message as any).data.version}`
219
- ),
220
- },
221
- receivesMessages.length > 0 &&
222
- renderMessages && {
223
- type: 'group',
224
- title: 'External Events',
225
- icon: 'Mail',
226
- pages: receivesMessages.map(
227
- (receive) => `${pluralizeMessageType(receive as any)}:${(receive as any).data.id}:${(receive as any).data.version}`
228
- ),
229
- },
230
308
  renderOwners && buildOwnersSection(owners),
231
309
  renderRepository && buildRepositorySection(domain.data.repository as { url: string; language: string }),
232
310
  hasAttachments && buildAttachmentsSection(domain.data.attachments as any[]),
@@ -165,7 +165,7 @@ export const buildMessageNode = (
165
165
  },
166
166
  renderFlows && {
167
167
  type: 'group',
168
- title: 'Flows',
168
+ title: 'Appears in flows',
169
169
  icon: 'Waypoints',
170
170
  pages: flowRefs,
171
171
  visible: flowRefs.length > 0,
@@ -115,7 +115,7 @@ export const buildServiceNode = (
115
115
  renderVisualiser &&
116
116
  hasDataStores && {
117
117
  type: 'item',
118
- title: 'Data Map',
118
+ title: 'Data Dependency Graph',
119
119
  href: buildUrl(`/visualiser/services/${service.data.id}/${service.data.version}/data`),
120
120
  },
121
121
  ].filter(Boolean) as ChildRef[],
@@ -202,7 +202,9 @@ export const buildServiceNode = (
202
202
  },
203
203
  hasFlows && {
204
204
  type: 'group',
205
- title: 'Flows',
205
+ // If the service declares its own flows it owns them ("Flows"); otherwise it is
206
+ // only referenced as a step in someone else's flow ("Appears in flows").
207
+ title: serviceFlows.length > 0 ? 'Flows' : 'Appears in flows',
206
208
  icon: 'Waypoints',
207
209
  pages: serviceFlowRefs,
208
210
  },
@@ -58,6 +58,7 @@ export type ResourceGroupContext = {
58
58
  agents?: CollectionEntry<'agents'>[];
59
59
  services: CollectionEntry<'services'>[];
60
60
  domains: CollectionEntry<'domains'>[];
61
+ systems?: CollectionEntry<'systems'>[];
61
62
  events: CollectionEntry<'events'>[];
62
63
  commands: CollectionEntry<'commands'>[];
63
64
  queries: CollectionEntry<'queries'>[];
@@ -115,7 +116,7 @@ export const buildArchitectureDecisionsSection = (resource: AdrResource, adrs: A
115
116
  return {
116
117
  type: 'group',
117
118
  title: 'Decision Records',
118
- icon: 'BookText',
119
+ icon: 'ClipboardList',
119
120
  pages: [...relatedAdrs].sort(byAdrName).map(getAdrNodeKey),
120
121
  };
121
122
  };
@@ -0,0 +1,166 @@
1
+ import type { CollectionEntry } from 'astro:content';
2
+ import { buildUrl } from '@utils/url-builder';
3
+ import type { NavNode, ChildRef, ResourceGroupContext } from './shared';
4
+ import {
5
+ buildQuickReferenceSection,
6
+ buildOwnersSection,
7
+ shouldRenderSideBarSection,
8
+ buildRepositorySection,
9
+ buildAttachmentsSection,
10
+ buildDiagramNavItems,
11
+ buildResourceDocsSection,
12
+ } from './shared';
13
+ import { isChangelogEnabled, isVisualiserEnabled } from '@utils/feature';
14
+ import { customIconFieldsForResource } from '@utils/icon';
15
+
16
+ // Sort resolved collection entries A-Z by their display name (falling back to id).
17
+ const byResourceName = (a: any, b: any) => (a.data?.name || a.data?.id || '').localeCompare(b.data?.name || b.data?.id || '');
18
+
19
+ export const buildSystemNode = (system: CollectionEntry<'systems'>, owners: any[], context: ResourceGroupContext): NavNode => {
20
+ const servicesInSystem = [...(system.data.services || [])].sort(byResourceName);
21
+ const renderServices = servicesInSystem.length > 0 && shouldRenderSideBarSection(system, 'services');
22
+
23
+ const flowsInSystem = [...(system.data.flows || [])].sort(byResourceName);
24
+ const renderFlows = flowsInSystem.length > 0 && shouldRenderSideBarSection(system, 'flows');
25
+
26
+ const entitiesInSystem = [...(system.data.entities || [])].sort(byResourceName);
27
+ const renderEntities = entitiesInSystem.length > 0 && shouldRenderSideBarSection(system, 'entities');
28
+
29
+ const containersInSystem = [...(system.data.containers || [])].sort(byResourceName);
30
+ const renderContainers = containersInSystem.length > 0 && shouldRenderSideBarSection(system, 'containers');
31
+
32
+ // The Resources page/link only makes sense when the system actually has resources
33
+ // attached (services, flows, entities or data stores). Mirrors what the page renders.
34
+ const hasResources =
35
+ servicesInSystem.length > 0 || flowsInSystem.length > 0 || entitiesInSystem.length > 0 || containersInSystem.length > 0;
36
+
37
+ const systemDiagrams = system.data.diagrams || [];
38
+ const diagramNavItems = buildDiagramNavItems(systemDiagrams, context.diagrams);
39
+ const hasDiagrams = diagramNavItems.length > 0 && shouldRenderSideBarSection(system, 'diagrams');
40
+
41
+ // A system that declares relationships to other systems, or actors, can be the starting
42
+ // point of a Context Diagram. (Systems that are only referenced by others still get a
43
+ // context page; here we surface the link from systems that declare something themselves.)
44
+ const hasRelationships = (system.data.relationships || []).length > 0 || (system.data.actors || []).length > 0;
45
+
46
+ const renderOwners = owners.length > 0 && shouldRenderSideBarSection(system, 'owners');
47
+ const renderRepository = system.data.repository && shouldRenderSideBarSection(system, 'repository');
48
+ const hasAttachments = system.data.attachments && system.data.attachments.length > 0;
49
+
50
+ const renderVisualiser = isVisualiserEnabled();
51
+
52
+ const docsSection = buildResourceDocsSection(
53
+ 'systems',
54
+ system.data.id,
55
+ system.data.version,
56
+ context.resourceDocs,
57
+ context.resourceDocCategories
58
+ );
59
+
60
+ return {
61
+ type: 'item',
62
+ title: system.data.name,
63
+ badge: 'System',
64
+ summary: system.data.summary,
65
+ // Systems use a custom icon when defined, otherwise none — the surrounding
66
+ // 'Systems' section header (and System badge) already convey the type, so the
67
+ // default Group glyph on every item is redundant.
68
+ ...customIconFieldsForResource(system.data),
69
+ pages: [
70
+ buildQuickReferenceSection(
71
+ [
72
+ {
73
+ title: 'Overview',
74
+ href: buildUrl(`/docs/systems/${system.data.id}/${system.data.version}`),
75
+ },
76
+ hasResources && {
77
+ title: 'System Resources',
78
+ href: buildUrl(`/docs/systems/${system.data.id}/${system.data.version}/resources`),
79
+ },
80
+ isChangelogEnabled() &&
81
+ shouldRenderSideBarSection(system, 'changelog') && {
82
+ title: 'Changelog',
83
+ href: buildUrl(`/docs/systems/${system.data.id}/${system.data.version}/changelog`),
84
+ },
85
+ ].filter(Boolean) as { title: string; href: string }[]
86
+ ),
87
+ docsSection,
88
+ {
89
+ type: 'group',
90
+ title: 'Architecture',
91
+ icon: 'Workflow',
92
+ pages: [
93
+ {
94
+ type: 'item',
95
+ title: 'Overview',
96
+ href: buildUrl(`/architecture/systems/${system.data.id}/${system.data.version}`),
97
+ },
98
+ renderVisualiser &&
99
+ hasRelationships && {
100
+ type: 'item',
101
+ title: 'Context Diagram',
102
+ href: buildUrl(`/visualiser/systems/${system.data.id}/${system.data.version}/context`),
103
+ },
104
+ renderVisualiser && {
105
+ type: 'item',
106
+ title: 'Resource Diagram',
107
+ href: buildUrl(`/visualiser/systems/${system.data.id}/${system.data.version}`),
108
+ },
109
+ ].filter(Boolean) as ChildRef[],
110
+ },
111
+ hasDiagrams && {
112
+ type: 'group',
113
+ title: 'Diagrams',
114
+ icon: 'FileImage',
115
+ pages: diagramNavItems,
116
+ },
117
+ (renderServices || renderFlows || renderContainers) && {
118
+ type: 'group',
119
+ title: 'Resources',
120
+ icon: 'Boxes',
121
+ // Resource type subsections are ordered A-Z by their title, and the
122
+ // resources within each subsection are ordered A-Z by name (sorted above).
123
+ pages: (
124
+ [
125
+ renderServices && {
126
+ type: 'group',
127
+ title: 'Services',
128
+ subtle: true,
129
+ icon: 'Server',
130
+ pages: servicesInSystem.map((service) => `service:${(service as any).data.id}:${(service as any).data.version}`),
131
+ },
132
+ renderFlows && {
133
+ type: 'group',
134
+ title: 'Flows',
135
+ subtle: true,
136
+ icon: 'Waypoints',
137
+ pages: flowsInSystem.map((flow) => `flow:${(flow as any).data.id}:${(flow as any).data.version}`),
138
+ },
139
+ renderContainers && {
140
+ type: 'group',
141
+ title: 'Data Stores',
142
+ subtle: true,
143
+ icon: 'Database',
144
+ pages: containersInSystem.map(
145
+ (container) => `container:${(container as any).data.id}:${(container as any).data.version}`
146
+ ),
147
+ },
148
+ ].filter(Boolean) as NavNode[]
149
+ ).sort((a, b) => a.title.localeCompare(b.title)) as ChildRef[],
150
+ },
151
+ renderEntities && {
152
+ type: 'group',
153
+ title: 'Entities',
154
+ icon: 'Box',
155
+ pages: entitiesInSystem.map((entity) => ({
156
+ type: 'item',
157
+ title: (entity as any).data?.name || (entity as any).data.id,
158
+ href: buildUrl(`/docs/entities/${(entity as any).data.id}/${(entity as any).data.version}`),
159
+ })),
160
+ },
161
+ renderOwners && buildOwnersSection(owners),
162
+ renderRepository && buildRepositorySection(system.data.repository as { url: string; language: string }),
163
+ hasAttachments && buildAttachmentsSection(system.data.attachments as any[]),
164
+ ].filter(Boolean) as ChildRef[],
165
+ };
166
+ };