@eventcatalog/core 4.10.12 → 4.10.14

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 (179) 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/{chunk-7URQ754Q.js → chunk-HERAHLM2.js} +1 -1
  6. package/dist/{chunk-Y5XU3YLT.js → chunk-LITOQH56.js} +1 -1
  7. package/dist/{chunk-S4LLGOO4.js → chunk-LUW6RDA7.js} +1 -1
  8. package/dist/{chunk-VFSEVDCQ.js → chunk-ONDFEA5R.js} +1 -1
  9. package/dist/{chunk-A53VCEEA.js → chunk-V2SKOQYF.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/docs/api/02-config.md +55 -1
  13. package/dist/docs/development/authentication/providers/03-setting-up-github.md +29 -0
  14. package/dist/docs/development/bring-your-own-documentation/custom-pages/02-adding-custom-docs.md +2 -2
  15. package/dist/docs/development/bring-your-own-documentation/custom-pages/03-components.md +17 -0
  16. package/dist/docs/development/components/07-resource-references.md +7 -1
  17. package/dist/docs/development/components/components/05-architecture-graph.md +65 -0
  18. package/dist/docs/development/components/components/{05-attachments.md → 06-attachments.md} +1 -1
  19. package/dist/docs/development/components/components/{06-channel-information.md → 07-channel-information.md} +1 -1
  20. package/dist/docs/development/components/components/{07-custom-properties.md → 08-custom-properties.md} +1 -1
  21. package/dist/docs/development/components/components/{08-design.md → 09-design.md} +1 -1
  22. package/dist/docs/development/components/components/{09-entitymap.md → 10-entitymap.md} +1 -1
  23. package/dist/docs/development/components/components/{10-flow.md → 11-flow.md} +1 -1
  24. package/dist/docs/development/components/components/{11-link.md → 12-link.md} +1 -1
  25. package/dist/docs/development/components/components/{12-mermaid-file-loader.md → 13-mermaid-file-loader.md} +1 -1
  26. package/dist/docs/development/components/components/{13-message-table.md → 14-message-table.md} +1 -1
  27. package/dist/docs/development/components/components/{14-nodegraph.md → 15-nodegraph.md} +1 -1
  28. package/dist/docs/development/components/components/{15-openapi.md → 16-openapi.md} +1 -1
  29. package/dist/docs/development/components/components/{16-prompt.md → 17-prompt.md} +1 -1
  30. package/dist/docs/development/components/components/{17-remote-schema.md → 18-remote-schema.md} +1 -1
  31. package/dist/docs/development/components/components/{18-resource-group-table.md → 19-resource-group-table.md} +1 -1
  32. package/dist/docs/development/components/components/{19-resource-link.md → 20-resource-link.md} +1 -1
  33. package/dist/docs/development/components/components/{20-schema.md → 21-schema.md} +1 -1
  34. package/dist/docs/development/components/components/{21-schema-viewer.md → 22-schema-viewer.md} +1 -1
  35. package/dist/docs/development/components/components/{22-steps.md → 23-steps.md} +1 -1
  36. package/dist/docs/development/components/components/{23-tabs.md → 24-tabs.md} +1 -1
  37. package/dist/docs/development/components/components/{24-tiles.md → 25-tiles.md} +1 -1
  38. package/dist/docs/development/components/components/{25-visibility.md → 26-visibility.md} +1 -1
  39. package/dist/docs/development/customization/04-documentation-sidebar.md +3 -3
  40. package/dist/docs/development/customization/05-resource-sidebar.md +235 -0
  41. package/dist/docs/development/customization/09-embed-eventcatalog.md +179 -0
  42. package/dist/docs/development/customization/_category_.json +1 -1
  43. package/dist/docs/development/deployment/build-and-deploy.md +4 -0
  44. package/dist/docs/development/deployment/hosting-options.md +1 -1
  45. package/dist/docs/development/deployment/link-validation.md +105 -0
  46. package/dist/docs/development/developer-tools/eventcatalog-linter/00-overview.md +76 -0
  47. package/dist/docs/development/developer-tools/eventcatalog-linter/01-setup.md +144 -0
  48. package/dist/docs/development/developer-tools/eventcatalog-linter/_category_.json +10 -0
  49. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/01-configure-rules.md +130 -0
  50. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/02-run-in-ci.md +93 -0
  51. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/03-use-custom-frontmatter.md +81 -0
  52. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/04-reference-external-catalogs.md +51 -0
  53. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/05-fix-common-problems.md +120 -0
  54. package/dist/docs/development/developer-tools/eventcatalog-linter/how-to/_category_.json +12 -0
  55. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/01-cli.md +109 -0
  56. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/02-configuration.md +120 -0
  57. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/03-rules.md +287 -0
  58. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/04-versions.md +77 -0
  59. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/05-supported-resources.md +75 -0
  60. package/dist/docs/development/developer-tools/eventcatalog-linter/reference/_category_.json +12 -0
  61. package/dist/docs/development/guides/97-architecture-graph.md +78 -0
  62. package/dist/docs/development/guides/domains/02-create-domain.md +1 -1
  63. package/dist/docs/development/guides/domains/03-ownership-and-language/02-adding-ubiquitous-language.md +29 -0
  64. package/dist/docs/development/guides/domains/09-reference.md +10 -0
  65. package/dist/docs/development/guides/resources/messages/04-message-channels/02-adding-channels.md +2 -2
  66. package/dist/docs/development/guides/resources/messages/04-message-channels/04-adding-messages-to-services.md +8 -0
  67. package/dist/docs/development/guides/resources/schemas/03-add-specifications-to-services/01-add-openapi-specifications.md +2 -2
  68. package/dist/docs/development/guides/resources/schemas/03-add-specifications-to-services/02-add-asyncapi-specifications.md +3 -3
  69. package/dist/docs/development/guides/systems/05-system-context-maps.md +2 -0
  70. package/dist/docs/development/license-keys/license-validation.md +7 -3
  71. package/dist/docs/development/license-keys/overview.md +7 -3
  72. package/dist/docs/development/upgrading/upgrading.md +3 -3
  73. package/dist/docs/development/upgrading/v2.md +3 -3
  74. package/dist/docs/federation/00-overview.md +158 -0
  75. package/dist/docs/federation/01-first-federation.md +199 -0
  76. package/dist/docs/federation/_category_.json +12 -0
  77. package/dist/docs/federation/explanation/_category_.json +12 -0
  78. package/dist/docs/federation/explanation/how-it-works.md +129 -0
  79. package/dist/docs/federation/explanation/lockfile-and-cache.md +134 -0
  80. package/dist/docs/federation/explanation/mvp-status-and-feedback.md +86 -0
  81. package/dist/docs/federation/explanation/ownership-and-references.md +134 -0
  82. package/dist/docs/federation/how-to/_category_.json +12 -0
  83. package/dist/docs/federation/how-to/configure-github-sources.md +156 -0
  84. package/dist/docs/federation/how-to/configure-validation-rules.md +127 -0
  85. package/dist/docs/federation/how-to/run-in-ci.md +138 -0
  86. package/dist/docs/federation/how-to/share-assets-and-components.md +125 -0
  87. package/dist/docs/federation/how-to/use-local-sources.md +138 -0
  88. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/00-introduction.md +11 -2
  89. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/01-installation.md +11 -3
  90. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/02-plugin-configuration.md +11 -2
  91. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/03-examples.md +11 -1
  92. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/04-configuration.md +11 -1
  93. package/dist/docs/{plugins/eventcatalog-federation → federation/legacy-federation}/05-setup-team-catalog.md +11 -2
  94. package/dist/docs/federation/legacy-federation/_category_.json +12 -0
  95. package/dist/docs/federation/reference/_category_.json +12 -0
  96. package/dist/docs/federation/reference/cli.md +122 -0
  97. package/dist/docs/federation/reference/configuration.md +192 -0
  98. package/dist/docs/federation/reference/diagnostic-rules.md +173 -0
  99. package/dist/docs/federation/reference/generated-output.md +118 -0
  100. package/dist/docs/federation/reference/supported-content.md +86 -0
  101. package/dist/docs/federation/reference/troubleshooting.md +208 -0
  102. package/dist/docs/plugins/01-intro.md +3 -3
  103. package/dist/docs/plugins/03-all-plugins.md +2 -2
  104. package/dist/docs/plugins/asyncapi/00-intro.md +4 -4
  105. package/dist/docs/plugins/asyncapi/02-plugin-configuration.md +4 -4
  106. package/dist/docs/plugins/backstage/00-overview.md +53 -0
  107. package/dist/docs/plugins/backstage/01-get-started.md +95 -0
  108. package/dist/docs/plugins/backstage/02-embed-entity-tabs.md +109 -0
  109. package/dist/docs/plugins/backstage/03-add-overview-cards.md +79 -0
  110. package/dist/docs/plugins/backstage/04-control-theme-and-size.md +54 -0
  111. package/dist/docs/plugins/backstage/05-migrate-to-annotations.md +49 -0
  112. package/dist/docs/plugins/backstage/06-configuration.md +85 -0
  113. package/dist/docs/plugins/backstage/07-components.md +129 -0
  114. package/dist/docs/plugins/backstage/08-how-embeds-work.md +52 -0
  115. package/dist/docs/plugins/backstage/_category_.json +4 -3
  116. package/dist/docs/plugins/eventbridge/00-intro.md +5 -5
  117. package/dist/docs/plugins/eventbridge/03-api.md +3 -3
  118. package/dist/docs/plugins/graphql/00-intro.md +2 -2
  119. package/dist/docs/plugins/graphql/02-plugin-configuration.md +2 -2
  120. package/dist/docs/plugins/hookdeck/01-intro.md +1 -1
  121. package/dist/docs/plugins/openapi/00-intro.md +1 -1
  122. package/dist/eventcatalog.cjs +1 -1
  123. package/dist/eventcatalog.js +5 -5
  124. package/dist/generate.cjs +1 -1
  125. package/dist/generate.js +3 -3
  126. package/dist/utils/cli-logger.cjs +1 -1
  127. package/dist/utils/cli-logger.js +2 -2
  128. package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +208 -119
  129. package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +28 -8
  130. package/eventcatalog/src/components/ChatPanel/OfflineReply.tsx +45 -0
  131. package/eventcatalog/src/components/Header.astro +11 -6
  132. package/eventcatalog/src/components/MDX/Design/Design.astro +2 -2
  133. package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +2 -2
  134. package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -2
  135. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -2
  136. package/eventcatalog/src/components/SchemaExplorer/AvroSchemaViewer.tsx +21 -19
  137. package/eventcatalog/src/components/SchemaExplorer/JSONSchemaViewer.tsx +79 -77
  138. package/eventcatalog/src/components/SchemaExplorer/ProtobufSchemaViewer.test.tsx +91 -0
  139. package/eventcatalog/src/components/SchemaExplorer/ProtobufSchemaViewer.tsx +115 -33
  140. package/eventcatalog/src/components/SchemaExplorer/SchemaViewerHover.test.tsx +33 -0
  141. package/eventcatalog/src/components/SchemaExplorer/protobuf-validation.test.ts +84 -0
  142. package/eventcatalog/src/components/SchemaExplorer/protobuf-validation.ts +149 -0
  143. package/eventcatalog/src/components/Search/Search.astro +11 -4
  144. package/eventcatalog/src/components/Settings/AssistantSettingsForm.tsx +29 -24
  145. package/eventcatalog/src/enterprise/custom-documentation/pages/docs/custom/index.astro +3 -3
  146. package/eventcatalog/src/pages/diagrams/[id]/[version]/index.astro +2 -2
  147. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +5 -3
  148. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +10 -4
  149. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/asyncapi/[filename].astro +2 -2
  150. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +3 -3
  151. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/spec/[filename].astro +2 -2
  152. package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -2
  153. package/eventcatalog/src/utils/feature.ts +1 -0
  154. package/eventcatalog/src/utils/protobuf-schema.ts +211 -16
  155. package/package.json +3 -3
  156. package/dist/docs/cli/channels.md +0 -180
  157. package/dist/docs/cli/commands.md +0 -183
  158. package/dist/docs/cli/custom-docs.md +0 -78
  159. package/dist/docs/cli/data-products.md +0 -177
  160. package/dist/docs/cli/data-stores.md +0 -166
  161. package/dist/docs/cli/diagrams.md +0 -147
  162. package/dist/docs/cli/domains.md +0 -280
  163. package/dist/docs/cli/entities.md +0 -138
  164. package/dist/docs/cli/events.md +0 -186
  165. package/dist/docs/cli/export.md +0 -27
  166. package/dist/docs/cli/governance.md +0 -24
  167. package/dist/docs/cli/messages.md +0 -69
  168. package/dist/docs/cli/queries.md +0 -183
  169. package/dist/docs/cli/services.md +0 -266
  170. package/dist/docs/cli/snapshots.md +0 -44
  171. package/dist/docs/cli/teams.md +0 -75
  172. package/dist/docs/cli/users.md +0 -75
  173. package/dist/docs/cli/utilities.md +0 -43
  174. package/dist/docs/development/developer-tools/eventcatalog-linter.md +0 -732
  175. package/dist/docs/plugins/backstage/00-intro.md +0 -67
  176. package/dist/docs/plugins/backstage/01-installation.md +0 -242
  177. package/dist/docs/plugins/backstage/02-api.md +0 -51
  178. package/dist/docs/plugins/backstage/03-examples.md +0 -12
  179. package/dist/docs/plugins/eventcatalog-federation/_category_.json +0 -11
@@ -1,9 +1,15 @@
1
- import { useState, useEffect } from 'react';
2
- import { BookOpen } from 'lucide-react';
1
+ import { useState, useEffect, useRef, useCallback } from 'react';
2
+ import { Sparkles } from 'lucide-react';
3
3
  import ChatPanel from './ChatPanel';
4
4
 
5
- const ChatPanelButton = () => {
5
+ const ChatPanelButton = ({ configured = false }: { configured?: boolean }) => {
6
6
  const [isOpen, setIsOpen] = useState(false);
7
+ const buttonRef = useRef<HTMLButtonElement>(null);
8
+ const closePanel = useCallback(() => {
9
+ setIsOpen(false);
10
+ buttonRef.current?.focus();
11
+ }, []);
12
+ const [shortcut, setShortcut] = useState('⌘I');
7
13
 
8
14
  // Listen for custom event to open chat panel from other components
9
15
  useEffect(() => {
@@ -11,22 +17,36 @@ const ChatPanelButton = () => {
11
17
  setIsOpen(true);
12
18
  };
13
19
 
20
+ setShortcut(/Mac|iPhone|iPad/.test(navigator.platform) ? '⌘I' : 'Ctrl I');
21
+ const handleShortcut = (event: KeyboardEvent) => {
22
+ if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === 'i') {
23
+ event.preventDefault();
24
+ setIsOpen(true);
25
+ }
26
+ };
27
+ window.addEventListener('keydown', handleShortcut);
14
28
  window.addEventListener('eventcatalog:open-chat', handleOpenChat);
15
- return () => window.removeEventListener('eventcatalog:open-chat', handleOpenChat);
29
+ return () => {
30
+ window.removeEventListener('keydown', handleShortcut);
31
+ window.removeEventListener('eventcatalog:open-chat', handleOpenChat);
32
+ };
16
33
  }, []);
17
34
 
18
35
  return (
19
36
  <>
20
37
  <button
38
+ ref={buttonRef}
21
39
  onClick={() => setIsOpen(true)}
22
- className="flex items-center gap-1.5 px-4 py-1.5 rounded-md bg-[rgb(var(--ec-card-bg))] hover:bg-[rgb(var(--ec-content-hover))] ring-1 ring-inset ring-[rgb(var(--ec-page-border))] shadow-xs transition-colors text-sm ml-[-1px]"
40
+ className="flex h-9 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-4 rounded-md bg-[rgb(var(--ec-card-bg))] hover:bg-[rgb(var(--ec-content-hover))] ring-1 ring-inset ring-[rgb(var(--ec-page-border))] shadow-xs transition-colors text-sm"
23
41
  aria-label="Open AI Assistant"
42
+ aria-keyshortcuts="Meta+i Control+i"
43
+ title={`Open Event Catalog Assistant (${shortcut})`}
24
44
  >
25
- <BookOpen size={14} className="text-[rgb(var(--ec-accent))]" />
26
- <span className="font-light text-[rgb(var(--ec-page-text-muted))]">Ask</span>
45
+ <Sparkles size={16} className="shrink-0 text-gray-500" aria-hidden="true" />
46
+ <span className="font-light text-gray-500">Ask</span>
27
47
  </button>
28
48
 
29
- <ChatPanel isOpen={isOpen} onClose={() => setIsOpen(false)} />
49
+ <ChatPanel configured={configured} isOpen={isOpen} onClose={closePanel} />
30
50
  </>
31
51
  );
32
52
  };
@@ -0,0 +1,45 @@
1
+ import { ArrowUpRight } from 'lucide-react';
2
+ import { buildUrl } from '@utils/url-builder';
3
+
4
+ const setupUrl = 'https://www.eventcatalog.dev/docs/development/ask-your-architecture/eventcatalog-assistant/configuration';
5
+ const introduction = 'Your whole catalog, connected to your AI.';
6
+ const description =
7
+ 'Bring your own model and I can help you understand your services, trace how events flow, and explore the impact of a change across your whole catalog.';
8
+ const privacy = 'You choose where your data is processed: on your own infrastructure or with a model provider you trust.';
9
+ const setup = 'I’m not connected to a model yet. Your catalog owner can connect one to get started.';
10
+
11
+ export const offlineReplyText = `${introduction}\n\n${description}\n\n${privacy}\n\n${setup}`;
12
+
13
+ export default function OfflineReply() {
14
+ return (
15
+ <div className="ec-chat-offline w-full space-y-4 py-2 text-[13px] leading-relaxed text-[rgb(var(--ec-content-text))]">
16
+ <div className="space-y-2">
17
+ <p className="text-base font-medium leading-snug text-[rgb(var(--ec-page-text))]">{introduction}</p>
18
+ <p>{description}</p>
19
+ </div>
20
+ <div className="space-y-3 rounded-xl border border-[rgb(var(--ec-accent)/0.15)] bg-[rgb(var(--ec-accent)/0.04)] p-4">
21
+ <div className="space-y-1">
22
+ <p className="font-medium text-[rgb(var(--ec-page-text))]">Your model. Your control.</p>
23
+ <p className="text-[rgb(var(--ec-page-text-muted))]">{privacy}</p>
24
+ </div>
25
+ <p>{setup}</p>
26
+ <a
27
+ href={setupUrl}
28
+ target="_blank"
29
+ rel="noreferrer"
30
+ className="inline-flex items-center gap-2 rounded-lg bg-[rgb(var(--ec-accent))] px-3 py-2 text-xs font-medium text-white transition-colors hover:bg-[rgb(var(--ec-accent-hover))] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[rgb(var(--ec-accent))]"
31
+ >
32
+ Connect your model
33
+ <ArrowUpRight size={14} aria-hidden="true" />
34
+ </a>
35
+ </div>
36
+ <p className="text-xs leading-relaxed text-[rgb(var(--ec-page-text-muted))]">
37
+ Don’t need the assistant?{' '}
38
+ <a href={buildUrl('/settings/assistant')} className="underline underline-offset-2 hover:text-[rgb(var(--ec-page-text))]">
39
+ Turn off Event Catalog Assistant
40
+ </a>{' '}
41
+ or set <code className="text-[11px]">chat.enabled: false</code> in your catalog configuration.
42
+ </p>
43
+ </div>
44
+ );
45
+ }
@@ -2,7 +2,12 @@
2
2
  import catalog from '@utils/eventcatalog-config/catalog';
3
3
  import Search from '@components/Search/Search.astro';
4
4
  import { buildUrl } from '@utils/url-builder';
5
- import { showEventCatalogBranding, showCustomBranding, isEventCatalogChatEnabled } from '@utils/feature';
5
+ import {
6
+ showEventCatalogBranding,
7
+ showCustomBranding,
8
+ isEventCatalogChatVisible,
9
+ isEventCatalogChatEnabled,
10
+ } from '@utils/feature';
6
11
  import { getSession } from 'auth-astro/server';
7
12
  import { isAuthEnabled, isSSR } from '@utils/feature';
8
13
  import { EnvironmentDropdown } from './EnvironmentDropdown';
@@ -33,15 +38,15 @@ const repositoryUrl = catalog?.repositoryUrl || 'https://github.com/event-catalo
33
38
  : 'left: var(--ec-vertical-nav-width, 14rem);'}
34
39
  >
35
40
  <div class="px-6">
36
- <div class="flex justify-between items-center">
37
- <div class="hidden lg:flex flex-grow items-center">
38
- <div class="w-full max-w-xl">
41
+ <div class="flex justify-between items-center gap-4">
42
+ <div class="flex min-w-0 flex-1 items-center gap-3">
43
+ <div class="hidden lg:block min-w-0 flex-1 max-w-xl">
39
44
  <Search />
40
45
  </div>
41
- {isEventCatalogChatEnabled() && <ChatPanelButton client:idle />}
46
+ {isEventCatalogChatVisible() && <ChatPanelButton configured={isEventCatalogChatEnabled()} client:idle />}
42
47
  </div>
43
48
 
44
- <div class="hidden md:block w-3/12">
49
+ <div class="hidden md:block shrink-0 ml-auto">
45
50
  {
46
51
  session ? (
47
52
  <div class="flex items-center space-x-4 justify-end pr-2">
@@ -7,10 +7,10 @@ import AstroNodeGraph from '../NodeGraph/AstroNodeGraph';
7
7
  // Visualiser styles ship in the page head so ClientRouter navigations keep them (see NodeGraph.astro).
8
8
  import '@eventcatalog/visualiser/styles-core.css';
9
9
 
10
- import { isVisualiserEnabled, isEventCatalogChatEnabled, isDevMode } from '@utils/feature';
10
+ import { isVisualiserEnabled, isEventCatalogChatVisible, isDevMode } from '@utils/feature';
11
11
  import { loadSavedLayout, applyLayoutToNodes, buildResourceKey } from '@utils/node-graphs/layout-persistence';
12
12
 
13
- const isChatEnabled = isEventCatalogChatEnabled();
13
+ const isChatEnabled = isEventCatalogChatVisible();
14
14
 
15
15
  let design: any;
16
16
  let id = 'design';
@@ -7,10 +7,10 @@ import AstroNodeGraph from '../NodeGraph/AstroNodeGraph';
7
7
  import '@eventcatalog/visualiser/styles-core.css';
8
8
  import { getVersionFromCollection } from '@utils/collections/versions';
9
9
  import { getServices } from '@utils/collections/services';
10
- import { isEventCatalogChatEnabled, isDevMode } from '@utils/feature';
10
+ import { isEventCatalogChatVisible, isDevMode } from '@utils/feature';
11
11
  import { loadSavedLayout, applyLayoutToNodes, buildResourceKey } from '@utils/node-graphs/layout-persistence';
12
12
 
13
- const isChatEnabled = isEventCatalogChatEnabled();
13
+ const isChatEnabled = isEventCatalogChatVisible();
14
14
 
15
15
  const { id, version = 'latest', maxHeight, includeKey = true, entities, collection = 'domains', ...rest } = Astro.props;
16
16
  let resource = null;
@@ -6,12 +6,12 @@ import AstroNodeGraph from '../NodeGraph/AstroNodeGraph';
6
6
  // Visualiser styles ship in the page head so ClientRouter navigations keep them (see NodeGraph.astro).
7
7
  import '@eventcatalog/visualiser/styles-core.css';
8
8
  import { getVersionFromCollection } from '@utils/collections/versions';
9
- import { isVisualiserEnabled, isEventCatalogChatEnabled, isDevMode } from '@utils/feature';
9
+ import { isVisualiserEnabled, isEventCatalogChatVisible, isDevMode } from '@utils/feature';
10
10
  import { loadSavedLayout, applyLayoutToNodes, buildResourceKey } from '@utils/node-graphs/layout-persistence';
11
11
  import { randomUUID } from 'node:crypto';
12
12
  import { parseMdxBooleanProp } from '@utils/markdown';
13
13
 
14
- const isChatEnabled = isEventCatalogChatEnabled();
14
+ const isChatEnabled = isEventCatalogChatVisible();
15
15
 
16
16
  const { id, version = 'latest', maxHeight, mode = 'simple' } = Astro.props;
17
17
  const includeKey = parseMdxBooleanProp(Astro.props.legend ?? Astro.props.includeKey, true);
@@ -29,11 +29,11 @@ import { pageDataLoader } from '@utils/page-loaders/page-data-loader';
29
29
  import { getNodesAndEdges as getNodesAndEdgesForContainer } from '@utils/node-graphs/container-node-graph';
30
30
  import { getNodesAndEdges as getNodesAndEdgesForChannel } from '@utils/node-graphs/channel-node-graph';
31
31
  import config from '@config';
32
- import { isEventCatalogChatEnabled, isDevMode } from '@utils/feature';
32
+ import { isEventCatalogChatVisible, isDevMode } from '@utils/feature';
33
33
  import { loadSavedLayout, applyLayoutToNodes, buildResourceKey } from '@utils/node-graphs/layout-persistence';
34
34
  import { compactVisualiserGraph } from '@utils/node-graphs/compact-visualiser-graph';
35
35
 
36
- const isChatEnabled = isEventCatalogChatEnabled();
36
+ const isChatEnabled = isEventCatalogChatVisible();
37
37
 
38
38
  interface Props {
39
39
  id: string;
@@ -138,28 +138,30 @@ const AvroField = ({ field, level, expand, showRequired }: AvroFieldProps) => {
138
138
 
139
139
  {/* Field details */}
140
140
  <div className="flex-grow min-w-0">
141
- <div className="flex flex-wrap items-baseline gap-x-1.5 gap-y-1">
142
- <span className="avro-field-name font-semibold text-[rgb(var(--ec-page-text))] text-sm">{field.name}</span>
143
- <span className="text-[rgb(var(--ec-accent))] font-mono text-xs">{formatAvroType(field.type)}</span>
144
- {showRequired && isRequired && (
145
- <span className="text-red-600 dark:text-red-400 text-xs ml-auto flex-shrink-0">required</span>
141
+ <div className="-mx-1 rounded px-1 transition-colors hover:bg-[rgb(var(--ec-content-hover))]">
142
+ <div className="flex flex-wrap items-baseline gap-x-1.5 gap-y-1">
143
+ <span className="avro-field-name font-semibold text-[rgb(var(--ec-page-text))] text-sm">{field.name}</span>
144
+ <span className="text-[rgb(var(--ec-accent))] font-mono text-xs">{formatAvroType(field.type)}</span>
145
+ {showRequired && isRequired && (
146
+ <span className="text-red-600 dark:text-red-400 text-xs ml-auto flex-shrink-0">required</span>
147
+ )}
148
+ </div>
149
+
150
+ {field.doc && <p className="text-[rgb(var(--ec-page-text-muted))] text-xs mt-1">{field.doc}</p>}
151
+
152
+ {/* Show enum values if present */}
153
+ {enumType && enumType.symbols && (
154
+ <div className="text-xs text-[rgb(var(--ec-page-text-muted))] mt-1">
155
+ Values:{' '}
156
+ {enumType.symbols.map((s: string) => (
157
+ <code key={s} className="bg-[rgb(var(--ec-content-hover))] px-1 rounded mx-0.5 text-[rgb(var(--ec-page-text))]">
158
+ {s}
159
+ </code>
160
+ ))}
161
+ </div>
146
162
  )}
147
163
  </div>
148
164
 
149
- {field.doc && <p className="text-[rgb(var(--ec-page-text-muted))] text-xs mt-1">{field.doc}</p>}
150
-
151
- {/* Show enum values if present */}
152
- {enumType && enumType.symbols && (
153
- <div className="text-xs text-[rgb(var(--ec-page-text-muted))] mt-1">
154
- Values:{' '}
155
- {enumType.symbols.map((s: string) => (
156
- <code key={s} className="bg-[rgb(var(--ec-content-hover))] px-1 rounded mx-0.5 text-[rgb(var(--ec-page-text))]">
157
- {s}
158
- </code>
159
- ))}
160
- </div>
161
- )}
162
-
163
165
  {/* Nested fields for record types */}
164
166
  {hasNested && recordType && (
165
167
  <div className={`avro-nested-content mt-2 ${!isExpanded ? 'hidden' : ''}`}>
@@ -315,86 +315,88 @@ const SchemaProperty = ({ name, details, isRequired, level, isListItem = false,
315
315
  )}
316
316
 
317
317
  <div className="flex-grow">
318
- <div className="flex justify-between items-baseline">
319
- <div>
320
- <span className="font-semibold text-[rgb(var(--ec-page-text))] text-sm">{name}</span>
321
- <span className="ml-1.5 text-[rgb(var(--ec-accent))] font-mono text-xs">
322
- {hasVariants ? (details.variantType === 'anyOf' ? 'anyOf' : 'oneOf') : formatType(details.type)}
323
- {hasType(details.type, 'array') && details.items?.type ? `[${formatType(details.items.type)}]` : ''}
324
- {details.format ? `<${details.format}>` : ''}
325
- {details._refPath && (
326
- <span className="text-blue-600 dark:text-blue-400 ml-1">→ {details._refName || details._refPath}</span>
327
- )}
328
- {details._refNotFound && <span className="text-red-600 dark:text-red-400 ml-1">❌ ref not found</span>}
329
- {details.const !== undefined && (
330
- <span>
331
- constant: <code>{details.const}</code>
332
- </span>
333
- )}
334
- </span>
335
- </div>
336
- {isRequired && <span className="text-red-600 dark:text-red-400 text-xs ml-3 flex-shrink-0">required</span>}
337
- </div>
338
-
339
- {details.description && <p className="text-[rgb(var(--ec-page-text-muted))] text-xs mt-0.5">{details.description}</p>}
340
- {details.title && details.title !== details.description && (
341
- <p className="text-[rgb(var(--ec-page-text-muted))] text-xs mt-0.5 italic">Title: {details.title}</p>
342
- )}
343
-
344
- <div className="text-xs text-[rgb(var(--ec-page-text-muted))] mt-0.5 space-y-0">
345
- {details.pattern && (
346
- <div>
347
- Match pattern:{' '}
348
- <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
349
- {details.pattern}
350
- </code>
351
- </div>
352
- )}
353
- {details.minimum !== undefined && (
354
- <div>
355
- Minimum:{' '}
356
- <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
357
- {details.minimum}
358
- </code>
359
- </div>
360
- )}
361
- {details.maximum !== undefined && (
362
- <div>
363
- Maximum:{' '}
364
- <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
365
- {details.maximum}
366
- </code>
367
- </div>
368
- )}
369
- {details.minLength !== undefined && (
318
+ <div className="-mx-1 rounded px-1 transition-colors hover:bg-[rgb(var(--ec-content-hover))]">
319
+ <div className="flex justify-between items-baseline">
370
320
  <div>
371
- Min length:{' '}
372
- <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
373
- {details.minLength}
374
- </code>
375
- </div>
376
- )}
377
- {details.maxLength !== undefined && (
378
- <div>
379
- Max length:{' '}
380
- <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
381
- {details.maxLength}
382
- </code>
383
- </div>
384
- )}
385
- {details.enum && (
386
- <div>
387
- <span className="text-xs inline-block">Allowed values:</span>
388
- {details.enum.map((val: any, idx: number) => (
389
- <span key={idx} className="text-xs">
390
- {' '}
391
- <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
392
- {val}
393
- </code>
394
- </span>
395
- ))}
321
+ <span className="font-semibold text-[rgb(var(--ec-page-text))] text-sm">{name}</span>
322
+ <span className="ml-1.5 text-[rgb(var(--ec-accent))] font-mono text-xs">
323
+ {hasVariants ? (details.variantType === 'anyOf' ? 'anyOf' : 'oneOf') : formatType(details.type)}
324
+ {hasType(details.type, 'array') && details.items?.type ? `[${formatType(details.items.type)}]` : ''}
325
+ {details.format ? `<${details.format}>` : ''}
326
+ {details._refPath && (
327
+ <span className="text-blue-600 dark:text-blue-400 ml-1">→ {details._refName || details._refPath}</span>
328
+ )}
329
+ {details._refNotFound && <span className="text-red-600 dark:text-red-400 ml-1">❌ ref not found</span>}
330
+ {details.const !== undefined && (
331
+ <span>
332
+ constant: <code>{details.const}</code>
333
+ </span>
334
+ )}
335
+ </span>
396
336
  </div>
337
+ {isRequired && <span className="text-red-600 dark:text-red-400 text-xs ml-3 flex-shrink-0">required</span>}
338
+ </div>
339
+
340
+ {details.description && <p className="text-[rgb(var(--ec-page-text-muted))] text-xs mt-0.5">{details.description}</p>}
341
+ {details.title && details.title !== details.description && (
342
+ <p className="text-[rgb(var(--ec-page-text-muted))] text-xs mt-0.5 italic">Title: {details.title}</p>
397
343
  )}
344
+
345
+ <div className="text-xs text-[rgb(var(--ec-page-text-muted))] mt-0.5 space-y-0">
346
+ {details.pattern && (
347
+ <div>
348
+ Match pattern:{' '}
349
+ <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
350
+ {details.pattern}
351
+ </code>
352
+ </div>
353
+ )}
354
+ {details.minimum !== undefined && (
355
+ <div>
356
+ Minimum:{' '}
357
+ <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
358
+ {details.minimum}
359
+ </code>
360
+ </div>
361
+ )}
362
+ {details.maximum !== undefined && (
363
+ <div>
364
+ Maximum:{' '}
365
+ <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
366
+ {details.maximum}
367
+ </code>
368
+ </div>
369
+ )}
370
+ {details.minLength !== undefined && (
371
+ <div>
372
+ Min length:{' '}
373
+ <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
374
+ {details.minLength}
375
+ </code>
376
+ </div>
377
+ )}
378
+ {details.maxLength !== undefined && (
379
+ <div>
380
+ Max length:{' '}
381
+ <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
382
+ {details.maxLength}
383
+ </code>
384
+ </div>
385
+ )}
386
+ {details.enum && (
387
+ <div>
388
+ <span className="text-xs inline-block">Allowed values:</span>
389
+ {details.enum.map((val: any, idx: number) => (
390
+ <span key={idx} className="text-xs">
391
+ {' '}
392
+ <code className="bg-[rgb(var(--ec-content-hover))] px-1 rounded text-[rgb(var(--ec-page-text))] font-thin py-0.5">
393
+ {val}
394
+ </code>
395
+ </span>
396
+ ))}
397
+ </div>
398
+ )}
399
+ </div>
398
400
  </div>
399
401
 
400
402
  {(hasNestedProperties || hasArrayItems || hasVariants) && (
@@ -0,0 +1,91 @@
1
+ import { renderToStaticMarkup } from 'react-dom/server';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { parseProtobufSchema } from '@utils/protobuf-schema';
4
+ import ProtobufSchemaViewer from './ProtobufSchemaViewer';
5
+
6
+ describe('ProtobufSchemaViewer', () => {
7
+ it('renders Protovalidate required and constraint annotations', () => {
8
+ const schema = parseProtobufSchema(`
9
+ syntax = "proto3";
10
+
11
+ enum Status {
12
+ STATUS_UNSPECIFIED = 0;
13
+ STATUS_ACTIVE = 1;
14
+ }
15
+
16
+ message Journey {
17
+ string id = 1 [
18
+ (buf.validate.field).required = true,
19
+ (buf.validate.field).string.uuid = true
20
+ ];
21
+ uint32 passenger_count = 2 [(buf.validate.field).uint32 = { gte: 1, lte: 20 }];
22
+ Status status = 3 [(buf.validate.field).enum = { defined_only: true, not_in: [0] }];
23
+ repeated string stops = 4 [(buf.validate.field).repeated = {
24
+ min_items: 2
25
+ unique: true
26
+ items: { string: { min_len: 3 } }
27
+ }];
28
+ planning.model.v1.TrainServiceReference train_service = 5;
29
+ optional string description = 6 [(datahub.v1.gdpr_rule).string = true];
30
+ }
31
+ `);
32
+
33
+ const html = renderToStaticMarkup(<ProtobufSchemaViewer schema={schema} />);
34
+ const statusField = html.slice(html.indexOf('>status</span>'), html.indexOf('>stops</span>'));
35
+
36
+ expect(html).toContain('required');
37
+ expect(html).toContain('Format');
38
+ expect(html).toContain('UUID');
39
+ expect(html).toContain('Minimum');
40
+ expect(html).toContain('Maximum');
41
+ expect(html).toContain('Defined enum values only');
42
+ expect(html).toContain('Excluded values');
43
+ expect(html).toContain('Minimum items');
44
+ expect(html).toContain('Unique items');
45
+ expect(html).toContain('Item minimum length');
46
+ expect(html).toContain('title="planning.model.v1.TrainServiceReference"');
47
+ expect(html).toContain('>TrainServiceReference</span>');
48
+ expect(statusField).not.toContain('STATUS_UNSPECIFIED');
49
+ expect(statusField).toContain('STATUS_ACTIVE');
50
+ expect(html).toContain('Annotation:');
51
+ expect(html).toContain('(datahub.v1.gdpr_rule).string');
52
+ expect(html).toContain('>true</code>');
53
+ });
54
+
55
+ it('filters enum values using repeated-item and map-value constraints', () => {
56
+ const schema = parseProtobufSchema(`
57
+ syntax = "proto3";
58
+
59
+ enum Status {
60
+ STATUS_UNSPECIFIED = 0;
61
+ STATUS_ACTIVE = 1;
62
+ STATUS_ARCHIVED = 2;
63
+ }
64
+
65
+ message Journey {
66
+ repeated Status statuses = 1 [(buf.validate.field).repeated = {
67
+ items: { enum: { in: [1] } }
68
+ }];
69
+ map<string, Status> status_by_key = 2 [(buf.validate.field).map = {
70
+ values: { enum: { not_in: [0, 2] } }
71
+ }];
72
+ Status status = 3 [(buf.validate.field).enum.const = 2];
73
+ }
74
+ `);
75
+
76
+ const html = renderToStaticMarkup(<ProtobufSchemaViewer schema={schema} />);
77
+ const repeatedField = html.slice(html.indexOf('>statuses</span>'), html.indexOf('>status_by_key</span>'));
78
+ const mapField = html.slice(html.indexOf('>status_by_key</span>'), html.indexOf('>status</span>'));
79
+ const directField = html.slice(html.indexOf('>status</span>'), html.indexOf('>Enum:</span>'));
80
+
81
+ expect(repeatedField).toContain('STATUS_ACTIVE');
82
+ expect(repeatedField).not.toContain('STATUS_UNSPECIFIED');
83
+ expect(repeatedField).not.toContain('STATUS_ARCHIVED');
84
+ expect(mapField).toContain('STATUS_ACTIVE');
85
+ expect(mapField).not.toContain('STATUS_UNSPECIFIED');
86
+ expect(mapField).not.toContain('STATUS_ARCHIVED');
87
+ expect(directField).toContain('STATUS_ARCHIVED');
88
+ expect(directField).not.toContain('STATUS_UNSPECIFIED');
89
+ expect(directField).not.toContain('STATUS_ACTIVE');
90
+ });
91
+ });