@eventcatalog/core 4.9.0 → 4.10.0

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 (60) 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-XQHS36GO.js → chunk-5XURY2JV.js} +1 -1
  6. package/dist/{chunk-3CGSSMTK.js → chunk-BDFTJVGN.js} +1 -1
  7. package/dist/{chunk-HKVOSQTD.js → chunk-BQXEWQ5J.js} +1 -1
  8. package/dist/{chunk-MPHX5C7M.js → chunk-HFAQXAG3.js} +1 -1
  9. package/dist/{chunk-LVGQ23LR.js → chunk-IC6XSXJO.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.js +5 -5
  14. package/dist/generate.cjs +1 -1
  15. package/dist/generate.js +3 -3
  16. package/dist/utils/cli-logger.cjs +1 -1
  17. package/dist/utils/cli-logger.js +2 -2
  18. package/eventcatalog/src/components/Badge.astro +3 -1
  19. package/eventcatalog/src/components/FieldsExplorer/FieldNodeGraph.tsx +0 -5
  20. package/eventcatalog/src/components/MDX/Design/Design.astro +2 -0
  21. package/eventcatalog/src/components/MDX/EntityMap/EntityMap.astro +2 -0
  22. package/eventcatalog/src/components/MDX/Flow/Flow.astro +2 -0
  23. package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +5 -7
  24. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +5 -0
  25. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +121 -88
  26. package/eventcatalog/src/components/SideNav/NestedSideBar/storage.ts +55 -7
  27. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.spec.ts +59 -15
  28. package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +53 -7
  29. package/eventcatalog/src/content.config.ts +70 -0
  30. package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +19 -0
  31. package/eventcatalog/src/enterprise/fields/pages/fields.astro +2 -0
  32. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +1 -1
  33. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/[docVersion]/index.astro +16 -15
  34. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId]/index.astro +21 -17
  35. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +1 -1
  36. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +4 -4
  37. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +16 -8
  38. package/eventcatalog/src/pages/docs/[type]/[id]/language/[dictionaryId]/index.astro +48 -65
  39. package/eventcatalog/src/pages/docs/[type]/[id]/language/index.astro +45 -18
  40. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +1 -1
  41. package/eventcatalog/src/pages/docs/users/[id]/index.astro +1 -1
  42. package/eventcatalog/src/pages/schemas/[type]/[id]/[version]/index.astro +5 -1
  43. package/eventcatalog/src/pages/triggers/[type]/[id]/[version]/index.astro +2 -0
  44. package/eventcatalog/src/pages/visualiser/designs/[id]/index.astro +2 -0
  45. package/eventcatalog/src/stores/sidebar-store/builders/adr.ts +96 -34
  46. package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +167 -75
  47. package/eventcatalog/src/stores/sidebar-store/builders/container.ts +135 -59
  48. package/eventcatalog/src/stores/sidebar-store/builders/data-product.ts +144 -58
  49. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +306 -188
  50. package/eventcatalog/src/stores/sidebar-store/builders/entity.ts +108 -39
  51. package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +147 -63
  52. package/eventcatalog/src/stores/sidebar-store/builders/message.ts +212 -121
  53. package/eventcatalog/src/stores/sidebar-store/builders/service.ts +223 -127
  54. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +3 -23
  55. package/eventcatalog/src/stores/sidebar-store/builders/system.ts +187 -97
  56. package/eventcatalog/src/stores/sidebar-store/custom-sidebar.ts +678 -0
  57. package/eventcatalog/src/stores/sidebar-store/state.ts +66 -36
  58. package/eventcatalog/src/styles/tailwind.css +82 -0
  59. package/eventcatalog/src/utils/collections/resource-docs.ts +1 -0
  60. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import type { SectionCollapsePreferences } from './utils';
1
+ import { createSectionCollapsePreferences, type SectionCollapsePreferences } from './utils';
2
2
 
3
3
  // ============================================
4
4
  // Local Storage Persistence
@@ -7,6 +7,7 @@ import type { SectionCollapsePreferences } from './utils';
7
7
  const STORAGE_KEY = 'eventcatalog-sidebar-nav';
8
8
  const SECTION_PREFERENCES_KEY = 'eventcatalog-sidebar-sections:v2';
9
9
  const FAVORITES_KEY = 'eventcatalog-sidebar-favorites';
10
+ const SCROLL_KEY = 'eventcatalog-sidebar-scroll';
10
11
 
11
12
  // ============================================
12
13
  // Types
@@ -47,13 +48,59 @@ export const loadState = (): PersistedState | null => {
47
48
  }
48
49
  };
49
50
 
51
+ // ============================================
52
+ // Scroll position
53
+ // ============================================
54
+
55
+ /**
56
+ * The sidebar remounts on every full page load, which resets its scroll offset to the
57
+ * top. Persist offsets (per tab) so the list looks stationary when you follow a link near
58
+ * the bottom. A capped map keyed by drill-down path, so scrolling one level doesn't lose
59
+ * another level's offset (root -> drill in -> scroll -> back keeps the root position).
60
+ */
61
+ const MAX_SCROLL_ENTRIES = 30;
62
+
63
+ export const saveScrollPosition = (pathKey: string, scrollTop: number): void => {
64
+ try {
65
+ const stored = sessionStorage.getItem(SCROLL_KEY);
66
+ const parsed = stored ? JSON.parse(stored) : {};
67
+ const offsets: Record<string, number> = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
68
+ // Re-insert so the map stays ordered by recency, then drop the oldest beyond the cap.
69
+ delete offsets[pathKey];
70
+ offsets[pathKey] = scrollTop;
71
+ const keys = Object.keys(offsets);
72
+ for (const stale of keys.slice(0, Math.max(0, keys.length - MAX_SCROLL_ENTRIES))) {
73
+ delete offsets[stale];
74
+ }
75
+ sessionStorage.setItem(SCROLL_KEY, JSON.stringify(offsets));
76
+ } catch (e) {
77
+ console.warn('Failed to save sidebar scroll position:', e);
78
+ }
79
+ };
80
+
81
+ export const loadScrollPosition = (pathKey: string): number | null => {
82
+ try {
83
+ const stored = sessionStorage.getItem(SCROLL_KEY);
84
+ if (!stored) return null;
85
+ const offsets = JSON.parse(stored);
86
+ const scrollTop = offsets && typeof offsets === 'object' ? offsets[pathKey] : undefined;
87
+ return typeof scrollTop === 'number' ? scrollTop : null;
88
+ } catch (e) {
89
+ console.warn('Failed to load sidebar scroll position:', e);
90
+ return null;
91
+ }
92
+ };
93
+
50
94
  // ============================================
51
95
  // Collapsed Sections
52
96
  // ============================================
53
97
 
54
98
  export const saveCollapsedSections = (preferences: SectionCollapsePreferences): void => {
55
99
  try {
56
- localStorage.setItem(SECTION_PREFERENCES_KEY, JSON.stringify({ expanded: [...preferences.expanded] }));
100
+ localStorage.setItem(
101
+ SECTION_PREFERENCES_KEY,
102
+ JSON.stringify({ expanded: [...preferences.expanded], collapsed: [...preferences.collapsed] })
103
+ );
57
104
  } catch (e) {
58
105
  console.warn('Failed to save collapsed sections:', e);
59
106
  }
@@ -64,15 +111,16 @@ export const loadCollapsedSections = (): SectionCollapsePreferences => {
64
111
  const stored = localStorage.getItem(SECTION_PREFERENCES_KEY);
65
112
  if (stored) {
66
113
  const preferences = JSON.parse(stored);
67
- return {
68
- expanded: new Set(Array.isArray(preferences.expanded) ? preferences.expanded : []),
69
- };
114
+ return createSectionCollapsePreferences(
115
+ Array.isArray(preferences.expanded) ? preferences.expanded : [],
116
+ Array.isArray(preferences.collapsed) ? preferences.collapsed : []
117
+ );
70
118
  }
71
119
 
72
- return { expanded: new Set() };
120
+ return createSectionCollapsePreferences();
73
121
  } catch (e) {
74
122
  console.warn('Failed to load collapsed sections:', e);
75
- return { expanded: new Set() };
123
+ return createSectionCollapsePreferences();
76
124
  }
77
125
  };
78
126
 
@@ -1,30 +1,40 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { canCollapseGroup, findNodeKeyByUrl, getGroupLabel, isGroupCollapsed } from './utils';
2
+ import {
3
+ canCollapseGroup,
4
+ createSectionCollapsePreferences,
5
+ findNodeKeyByUrl,
6
+ getDefaultCollapsedState,
7
+ isGroupCollapsed,
8
+ toggleGroupCollapsed,
9
+ } from './utils';
3
10
 
4
- const preferences = (expanded: string[] = []) => ({
5
- expanded: new Set(expanded),
6
- });
11
+ const preferences = (expanded: string[] = [], collapsed: string[] = []) => createSectionCollapsePreferences(expanded, collapsed);
7
12
 
8
13
  describe('sidebar group presentation', () => {
9
- it('includes the visible child count in the group label', () => {
10
- expect(getGroupLabel('Outbound Messages', 10)).toBe('Outbound Messages (10)');
14
+ it('keeps root-level groups expanded', () => {
15
+ expect(canCollapseGroup(true)).toBe(false);
11
16
  });
12
17
 
13
- it.each(['Quick Reference', 'Architecture', 'Resources'])('does not include the child count for %s', (title) => {
14
- expect(getGroupLabel(title, 10)).toBe(title);
18
+ it('lets every group inside a resource sidebar collapse, whatever its size', () => {
19
+ expect(canCollapseGroup(false)).toBe(true);
20
+ expect(canCollapseGroup(false)).toBe(true);
15
21
  });
16
22
 
17
- it('keeps top-level groups expanded', () => {
18
- expect(canCollapseGroup(6, true)).toBe(false);
23
+ it('keeps groups marked as non-collapsible expanded regardless of their size', () => {
24
+ expect(canCollapseGroup(false, false)).toBe(false);
19
25
  });
20
26
 
21
- it('only allows nested groups with more than five children to collapse', () => {
22
- expect(canCollapseGroup(5, false)).toBe(false);
23
- expect(canCollapseGroup(6, false)).toBe(true);
27
+ it('starts long lists collapsed and short ones open unless told otherwise', () => {
28
+ expect(getDefaultCollapsedState(5)).toBe(false);
29
+ expect(getDefaultCollapsedState(6)).toBe(true);
30
+ expect(getDefaultCollapsedState(6, false)).toBe(false);
31
+ expect(getDefaultCollapsedState(2, true)).toBe(true);
24
32
  });
25
33
 
26
- it('keeps groups marked as non-collapsible expanded regardless of their size', () => {
27
- expect(canCollapseGroup(100, false, false)).toBe(false);
34
+ it('always allows collapsing when an explicit collapsed state is set, overriding every other rule', () => {
35
+ expect(canCollapseGroup(false, true, true)).toBe(true);
36
+ expect(canCollapseGroup(false, true, false)).toBe(true);
37
+ expect(canCollapseGroup(true, false, true)).toBe(true);
28
38
  });
29
39
  });
30
40
 
@@ -40,6 +50,40 @@ describe('isGroupCollapsed', () => {
40
50
  it('uses an explicit expanded preference', () => {
41
51
  expect(isGroupCollapsed(true, 'outbound-messages', preferences(['outbound-messages']))).toBe(false);
42
52
  });
53
+
54
+ it('starts expanded when the group defaults to expanded, until the user collapses it', () => {
55
+ expect(isGroupCollapsed(true, 'owners', preferences(), false)).toBe(false);
56
+ expect(isGroupCollapsed(true, 'owners', preferences([], ['owners']), false)).toBe(true);
57
+ });
58
+ });
59
+
60
+ describe('toggleGroupCollapsed', () => {
61
+ it('records the user overriding a collapsed-by-default group, then clears it on the way back', () => {
62
+ const opened = toggleGroupCollapsed('g', preferences());
63
+ expect([...opened.expanded]).toEqual(['g']);
64
+ expect(opened.collapsed.size).toBe(0);
65
+
66
+ const closedAgain = toggleGroupCollapsed('g', opened);
67
+ expect(closedAgain.expanded.size).toBe(0);
68
+ expect([...closedAgain.collapsed]).toEqual(['g']);
69
+ });
70
+
71
+ it('records the user overriding an expanded-by-default group', () => {
72
+ const closed = toggleGroupCollapsed('g', preferences(), false);
73
+ expect([...closed.collapsed]).toEqual(['g']);
74
+ expect(isGroupCollapsed(true, 'g', closed, false)).toBe(true);
75
+
76
+ const reopened = toggleGroupCollapsed('g', closed, false);
77
+ expect(reopened.collapsed.size).toBe(0);
78
+ expect([...reopened.expanded]).toEqual(['g']);
79
+ });
80
+
81
+ it('does not mutate the previous preferences', () => {
82
+ const before = preferences();
83
+ toggleGroupCollapsed('g', before);
84
+ expect(before.expanded.size).toBe(0);
85
+ expect(before.collapsed.size).toBe(0);
86
+ });
43
87
  });
44
88
 
45
89
  describe('findNodeKeyByUrl', () => {
@@ -1,20 +1,66 @@
1
1
  // Shared utilities for NestedSideBar components
2
2
 
3
3
  export const SIDEBAR_GROUP_COLLAPSE_THRESHOLD = 5;
4
- const GROUP_TITLES_WITHOUT_COUNT = new Set(['Quick Reference', 'Architecture', 'Resources']);
5
4
 
5
+ /**
6
+ * The user's overrides of each group's default collapse state, keyed by group id.
7
+ * A group is in at most one set; absence means "use the group's default".
8
+ */
6
9
  export type SectionCollapsePreferences = {
7
10
  expanded: Set<string>;
11
+ collapsed: Set<string>;
12
+ };
13
+
14
+ export const createSectionCollapsePreferences = (
15
+ expanded: Iterable<string> = [],
16
+ collapsed: Iterable<string> = []
17
+ ): SectionCollapsePreferences => ({
18
+ expanded: new Set(expanded),
19
+ collapsed: new Set(collapsed),
20
+ });
21
+
22
+ /**
23
+ * Whether a group shows a caret and can be toggled. Every group inside a resource's sidebar
24
+ * can collapse (docs-sidebar behaviour); the root catalog level stays open. An explicit
25
+ * `collapsed` (set via sidebar.json) overrides every other rule, including `collapsible: false`.
26
+ */
27
+ export const canCollapseGroup = (isTopLevel: boolean, collapsible = true, collapsed?: boolean): boolean => {
28
+ if (collapsed !== undefined) return true;
29
+ return collapsible && !isTopLevel;
8
30
  };
9
31
 
10
- export const canCollapseGroup = (childCount: number, isTopLevel: boolean, collapsible = true): boolean =>
11
- collapsible && !isTopLevel && childCount > SIDEBAR_GROUP_COLLAPSE_THRESHOLD;
32
+ /**
33
+ * The state a group starts in before the user touches it: an explicit `collapsed` wins,
34
+ * otherwise long lists start collapsed and short ones start open.
35
+ */
36
+ export const getDefaultCollapsedState = (childCount: number, collapsed?: boolean): boolean =>
37
+ collapsed ?? childCount > SIDEBAR_GROUP_COLLAPSE_THRESHOLD;
12
38
 
13
- export const getGroupLabel = (title: string, childCount: number): string =>
14
- GROUP_TITLES_WITHOUT_COUNT.has(title) ? title : `${title} (${childCount})`;
39
+ export const isGroupCollapsed = (
40
+ canCollapse: boolean,
41
+ groupId: string,
42
+ preferences: SectionCollapsePreferences,
43
+ defaultCollapsed = true
44
+ ): boolean => {
45
+ if (!canCollapse) return false;
46
+ if (preferences.expanded.has(groupId)) return false;
47
+ if (preferences.collapsed.has(groupId)) return true;
48
+ return defaultCollapsed;
49
+ };
15
50
 
16
- export const isGroupCollapsed = (canCollapse: boolean, groupId: string, preferences: SectionCollapsePreferences): boolean => {
17
- return canCollapse && !preferences.expanded.has(groupId);
51
+ /** Flip a group's state, recording it as an override of its default. */
52
+ export const toggleGroupCollapsed = (
53
+ groupId: string,
54
+ preferences: SectionCollapsePreferences,
55
+ defaultCollapsed = true
56
+ ): SectionCollapsePreferences => {
57
+ const next = createSectionCollapsePreferences(preferences.expanded, preferences.collapsed);
58
+ const isCurrentlyCollapsed = isGroupCollapsed(true, groupId, preferences, defaultCollapsed);
59
+ next.expanded.delete(groupId);
60
+ next.collapsed.delete(groupId);
61
+ if (isCurrentlyCollapsed) next.expanded.add(groupId);
62
+ else next.collapsed.add(groupId);
63
+ return next;
18
64
  };
19
65
 
20
66
  /**
@@ -1199,6 +1199,73 @@ const schemas = defineCollection({
1199
1199
  .extend(baseSchema.shape),
1200
1200
  });
1201
1201
 
1202
+ // Optional `sidebar.json` next to a resource's index.mdx. When present it fully
1203
+ // replaces the generated sidebar for that resource (see stores/sidebar-store/custom-sidebar.ts).
1204
+ const sidebarLinkSchema = z
1205
+ .object({
1206
+ title: z.string(),
1207
+ href: z.string(),
1208
+ icon: z.string().optional(),
1209
+ })
1210
+ .strict();
1211
+
1212
+ // { "title": "Runbooks", "icon": "Siren", "collapsed": true, "pages": [...] } (custom group).
1213
+ // Groups nest: a page can itself be a group, rendered as a subsection.
1214
+ type SidebarGroupInput = {
1215
+ title: string;
1216
+ icon?: string;
1217
+ collapsed?: boolean;
1218
+ pages: Array<string | z.input<typeof sidebarLinkSchema> | SidebarGroupInput>;
1219
+ };
1220
+ const sidebarGroupSchema: z.ZodType<SidebarGroupInput> = z.lazy(() =>
1221
+ z
1222
+ .object({
1223
+ title: z.string(),
1224
+ icon: z.string().optional(),
1225
+ collapsed: z.boolean().optional(),
1226
+ // "$quick-reference", "[[service|OrderService@1.0.0]]", "[[doc|guides/sla]]", { title, href } or a nested group
1227
+ pages: z.array(z.union([z.string(), sidebarLinkSchema, sidebarGroupSchema])),
1228
+ })
1229
+ .strict()
1230
+ );
1231
+
1232
+ const sidebarSchema = z
1233
+ .object({
1234
+ $schema: z.string().optional(),
1235
+ sections: z.array(
1236
+ z.union([
1237
+ // "$quick-reference" (predefined section)
1238
+ z.string(),
1239
+ // { "section": "$owners", "title": "Team", "collapsed": false } (predefined section, adjusted)
1240
+ z
1241
+ .object({
1242
+ section: z.string(),
1243
+ title: z.string().optional(),
1244
+ icon: z.string().optional(),
1245
+ collapsed: z.boolean().optional(),
1246
+ })
1247
+ .strict(),
1248
+ sidebarGroupSchema,
1249
+ ])
1250
+ ),
1251
+ })
1252
+ .strict();
1253
+
1254
+ const sidebars = defineCollection({
1255
+ loader: globWithSafeWatcher({
1256
+ // Anchored to the resource roots (like resourceDocCategories) so an unrelated
1257
+ // sidebar.json elsewhere in the project — or the public/generated mirror — is ignored.
1258
+ pattern: withIgnoredBuildArtifacts(
1259
+ withFederatedContent([
1260
+ '{services,events,commands,queries,flows,containers,channels,entities,data-products,systems,agents,adrs}/**/sidebar.json',
1261
+ 'domains/**/sidebar.json',
1262
+ ])
1263
+ ),
1264
+ base: projectDirBase,
1265
+ }),
1266
+ schema: sidebarSchema,
1267
+ });
1268
+
1202
1269
  export const collections = {
1203
1270
  events,
1204
1271
  commands,
@@ -1236,4 +1303,7 @@ export const collections = {
1236
1303
 
1237
1304
  // Generated from message schema references
1238
1305
  schemas,
1306
+
1307
+ // Optional per-resource sidebar overrides (sidebar.json)
1308
+ sidebars,
1239
1309
  };
@@ -71,6 +71,25 @@ type InferredResource = {
71
71
  resourceVersion: string;
72
72
  };
73
73
 
74
+ const DOC_TYPE_LABELS: Record<string, string> = {
75
+ adrs: 'ADR',
76
+ runbooks: 'Runbook',
77
+ contracts: 'Contract',
78
+ troubleshooting: 'Troubleshooting',
79
+ guides: 'Guide',
80
+ };
81
+
82
+ /** Human label for a doc type folder, e.g. `guides` → "Guide", `release-notes` → "Release Notes". */
83
+ export const getResourceDocTypeLabel = (type: string): string => {
84
+ if (DOC_TYPE_LABELS[type]) return DOC_TYPE_LABELS[type];
85
+ const normalized = type
86
+ .split(/[-_\s]+/)
87
+ .filter(Boolean)
88
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
89
+ .join(' ');
90
+ return normalized || 'Doc';
91
+ };
92
+
74
93
  let memoryCache: ResourceDocEntry[] | null = null;
75
94
  let memoryCategoryCache: ResourceDocCategoryEntry[] | null = null;
76
95
  let memoryResourceLookupCache: Record<ResourceCollection, ResourceLookup> | null = null;
@@ -1,4 +1,6 @@
1
1
  ---
2
+ // Visualiser styles ship in the page head so ClientRouter navigations keep them (see NodeGraph.astro).
3
+ import '@eventcatalog/visualiser/styles-core.css';
2
4
  import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
3
5
  import FieldsExplorer from '@components/FieldsExplorer/FieldsExplorer';
4
6
  import { isEventCatalogScaleEnabled } from '@utils/feature';
@@ -24,7 +24,7 @@ const specifications = type === 'services' ? getSpecificationsForService(props)
24
24
  <VerticalSideBarLayout title={pageTitle}>
25
25
  <main class="flex docs-layout min-h-full bg-[rgb(var(--ec-page-bg))]">
26
26
  <div class="flex docs-layout w-full">
27
- <div class="w-full lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
27
+ <div class="w-full min-w-0 lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
28
28
  {type === 'domains' && <DomainGrid domain={domain} client:load />}
29
29
  {type === 'systems' && <SystemGrid system={props} client:load />}
30
30
  {
@@ -8,7 +8,8 @@ import CopyAsMarkdown from '@components/CopyAsMarkdown';
8
8
  import Badge from '@components/Badge.astro';
9
9
  import { buildUrl } from '@utils/url-builder';
10
10
  import { AlignLeftIcon, HistoryIcon } from 'lucide-react';
11
- import { isEventCatalogChatEnabled, isResourceDocsEnabled } from '@utils/feature';
11
+ import { isEventCatalogChatEnabled, isMarkdownDownloadEnabled, isResourceDocsEnabled } from '@utils/feature';
12
+ import { getResourceDocTypeLabel } from '@utils/collections/resource-docs';
12
13
  import { getIcon } from '@utils/badges';
13
14
  import { collectionToResourceMap } from '@utils/collections/util';
14
15
 
@@ -51,28 +52,28 @@ const pagefindAttributes =
51
52
  <VerticalSideBarLayout title={pageTitle} description={props.data.summary}>
52
53
  <main class="flex docs-layout min-h-full bg-[rgb(var(--ec-page-bg))]" {...pagefindAttributes}>
53
54
  <div class="flex docs-layout w-full">
54
- <div class="w-full lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
55
- <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-2">
56
- <div>
57
- <div class="flex items-center">
58
- <div class="flex items-center gap-2">
59
- <h2 id="doc-page-header" class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{title}</h2>
60
- </div>
61
- </div>
55
+ <div class="w-full min-w-0 lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
56
+ {/* Header mirrors the latest-doc page ([docId]/index.astro) and the resource page. */}
57
+ <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-3 pt-4">
58
+ <div class="flex flex-col gap-4">
59
+ <span class="text-xs md:text-sm font-semibold text-[rgb(var(--ec-accent))] capitalize">
60
+ {getResourceDocTypeLabel(props.data.type)}
61
+ </span>
62
+ <h2 id="doc-page-header" class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{title}</h2>
62
63
  </div>
63
64
  {
64
65
  props.data.summary && (
65
- <p class="text-base pt-2 text-[rgb(var(--ec-page-text-muted))] font-light">{props.data.summary}</p>
66
+ <p class="text-base pt-4 text-[rgb(var(--ec-page-text-muted))] font-light">{props.data.summary}</p>
66
67
  )
67
68
  }
68
69
  {
69
70
  badges.length > 0 && (
70
- <div class="flex flex-wrap gap-1.5 py-3">
71
+ <div class="flex flex-wrap gap-1.5 pt-4">
71
72
  {badges.map((badge: any) => <Badge badge={badge} />)}
72
73
  </div>
73
74
  )
74
75
  }
75
- <div class="pt-4">
76
+ <div class="pt-5">
76
77
  <CopyAsMarkdown
77
78
  client:only="react"
78
79
  variant="toolbar"
@@ -80,9 +81,9 @@ const pagefindAttributes =
80
81
  chatQuery={chatQuery}
81
82
  chatEnabled={chatEnabled}
82
83
  editUrl=""
83
- markdownDownloadEnabled={true}
84
+ markdownDownloadEnabled={isMarkdownDownloadEnabled()}
84
85
  rssFeedEnabled={false}
85
- preferChatAsDefault={false}
86
+ preferChatAsDefault={chatEnabled}
86
87
  />
87
88
  </div>
88
89
  </div>
@@ -100,7 +101,7 @@ const pagefindAttributes =
100
101
  )
101
102
  }
102
103
  </div>
103
- <div class="prose py-4 max-w-none">
104
+ <div class="prose prose-md pt-8 pb-4 w-full text-[15px]">
104
105
  <Content components={components(props)} />
105
106
  </div>
106
107
  </div>
@@ -8,9 +8,10 @@ import CopyAsMarkdown from '@components/CopyAsMarkdown';
8
8
  import Badge from '@components/Badge.astro';
9
9
  import { buildUrl } from '@utils/url-builder';
10
10
  import { AlignLeftIcon, HistoryIcon } from 'lucide-react';
11
- import { isEventCatalogChatEnabled, isResourceDocsEnabled } from '@utils/feature';
11
+ import { isEventCatalogChatEnabled, isMarkdownDownloadEnabled, isResourceDocsEnabled } from '@utils/feature';
12
12
  import { getIcon } from '@utils/badges';
13
13
  import { collectionToResourceMap } from '@utils/collections/util';
14
+ import { getResourceDocTypeLabel } from '@utils/collections/resource-docs';
14
15
 
15
16
  import { Page } from './_index.data';
16
17
 
@@ -36,6 +37,7 @@ const docsBasePath = `/docs/${props.data.resourceCollection}/${props.data.resour
36
37
  const singularResourceName =
37
38
  collectionToResourceMap[props.data.resourceCollection as keyof typeof collectionToResourceMap] ??
38
39
  props.data.resourceCollection.slice(0, props.data.resourceCollection.length - 1);
40
+ const typeLabel = getResourceDocTypeLabel(props.data.type);
39
41
  const chatEnabled = isEventCatalogChatEnabled();
40
42
  const chatQuery = `Tell me about the ${props.data.type} doc "${title}" for ${props.data.resourceId} (version ${props.data.version})`;
41
43
 
@@ -51,20 +53,22 @@ const pagefindAttributes =
51
53
  <VerticalSideBarLayout title={pageTitle} description={props.data.summary}>
52
54
  <main class="flex docs-layout min-h-full bg-[rgb(var(--ec-page-bg))]" {...pagefindAttributes}>
53
55
  <div class="flex docs-layout w-full">
54
- <div class="w-full lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
55
- <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-2">
56
+ <div class="w-full min-w-0 lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
57
+ {/* Header mirrors the resource page (docs/[type]/[id]/[version]/index.astro) so docs feel like part of the resource. */}
58
+ <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-3 pt-4">
56
59
  <div class="flex flex-col gap-4">
57
- <div class="flex items-center gap-4">
58
- <h2 id="doc-page-header" class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{title}</h2>
59
- </div>
60
- {props.data.summary && <p class="text-base text-[rgb(var(--ec-page-text-muted))] font-light">{props.data.summary}</p>}
61
- {
62
- badges.length > 0 && (
63
- <div class="flex flex-wrap gap-1.5 py-1">
64
- {badges.map((badge: any) => <Badge badge={badge} />)}
65
- </div>
66
- )
67
- }
60
+ <span class="text-xs md:text-sm font-semibold text-[rgb(var(--ec-accent))] capitalize">{typeLabel}</span>
61
+ <h2 id="doc-page-header" class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{title}</h2>
62
+ </div>
63
+ {props.data.summary && <p class="text-base pt-4 text-[rgb(var(--ec-page-text-muted))] font-light">{props.data.summary}</p>}
64
+ {
65
+ badges.length > 0 && (
66
+ <div class="flex flex-wrap gap-1.5 pt-4">
67
+ {badges.map((badge: any) => <Badge badge={badge} />)}
68
+ </div>
69
+ )
70
+ }
71
+ <div class="pt-5">
68
72
  <CopyAsMarkdown
69
73
  client:only="react"
70
74
  variant="toolbar"
@@ -72,9 +76,9 @@ const pagefindAttributes =
72
76
  chatQuery={chatQuery}
73
77
  chatEnabled={chatEnabled}
74
78
  editUrl=""
75
- markdownDownloadEnabled={true}
79
+ markdownDownloadEnabled={isMarkdownDownloadEnabled()}
76
80
  rssFeedEnabled={false}
77
- preferChatAsDefault={false}
81
+ preferChatAsDefault={chatEnabled}
78
82
  />
79
83
  </div>
80
84
  </div>
@@ -92,7 +96,7 @@ const pagefindAttributes =
92
96
  )
93
97
  }
94
98
  </div>
95
- <div class="prose py-4 max-w-none">
99
+ <div class="prose prose-md pt-8 pb-4 w-full text-[15px]">
96
100
  <Content components={components(props)} />
97
101
  </div>
98
102
  </div>
@@ -66,7 +66,7 @@ const pagefindAttributes =
66
66
  <VerticalSideBarLayout title={pageTitle} description={`GraphQL schema for ${data.name}`}>
67
67
  <main class="flex docs-layout min-h-full bg-[rgb(var(--ec-page-bg))]" {...pagefindAttributes}>
68
68
  <div class="flex docs-layout w-full">
69
- <div class="w-full lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
69
+ <div class="w-full min-w-0 lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
70
70
  <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-2">
71
71
  <div>
72
72
  <div class="flex justify-between items-start">
@@ -408,8 +408,8 @@ if (!isAdrPage && !hasCurrentFlowEmbed && !hasCurrentPageNodeGraph) {
408
408
  <VerticalSideBarLayout title={pageTitle} description={props.data.summary}>
409
409
  <main class="flex docs-layout min-h-full bg-[rgb(var(--ec-page-bg))]" {...pagefindAttributes}>
410
410
  <div class="flex docs-layout w-full">
411
- <div class="w-full lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
412
- <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-6 pt-4">
411
+ <div class="w-full min-w-0 lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
412
+ <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-3 pt-4">
413
413
  <div>
414
414
  <div>
415
415
  <div class="flex flex-col gap-4">
@@ -447,7 +447,7 @@ if (!isAdrPage && !hasCurrentFlowEmbed && !hasCurrentPageNodeGraph) {
447
447
  </div>
448
448
  </div>
449
449
 
450
- <h2 class="text-base pt-4 text-[rgb(var(--ec-page-text-muted))] font-light">{props.data.summary}</h2>
450
+ <p class="text-base pt-4 text-[rgb(var(--ec-page-text-muted))] font-light">{props.data.summary}</p>
451
451
  {
452
452
  badges && (
453
453
  <div class="flex flex-wrap gap-1.5 pt-4">
@@ -596,7 +596,7 @@ if (!isAdrPage && !hasCurrentFlowEmbed && !hasCurrentPageNodeGraph) {
596
596
  }
597
597
  </div>
598
598
 
599
- <div class="prose prose-md py-4 w-full text-[15px]">
599
+ <div class="prose prose-md pt-8 pb-4 w-full text-[15px]">
600
600
  <Content components={components(props)} />
601
601
  </div>
602
602
  <div data-pagefind-ignore>
@@ -29,6 +29,8 @@ const hrefForResource = (collection: SystemResourceCollection, id: string, versi
29
29
  return buildUrl(`/docs/${collection}/${id}/${version}`);
30
30
  };
31
31
 
32
+ const ownerTypeLabel = ownerCollection === 'domains' ? 'Domain' : 'System';
33
+
32
34
  const resources: SystemResourceItem[] = resourceGroups.flatMap(({ collection, items }) =>
33
35
  items.map((item: any) => ({
34
36
  collection,
@@ -45,16 +47,22 @@ const resources: SystemResourceItem[] = resourceGroups.flatMap(({ collection, it
45
47
  <VerticalSideBarLayout title={`${data.name} | Resources`}>
46
48
  <main class="flex docs-layout min-h-full bg-[rgb(var(--ec-page-bg))]">
47
49
  <div class="flex docs-layout w-full">
48
- <div class="w-full lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
49
- <div class="border-b border-[rgb(var(--ec-page-border))] pb-4">
50
- <h2 class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">{data.name} Resources</h2>
51
- <h2 class="text-lg pt-2 text-[rgb(var(--ec-page-text-muted))] font-light">
52
- A group of resources that are mapped to the {data.name}{' '}
53
- {ownerCollection === 'domains' ? 'domain' : 'system'}.
54
- </h2>
50
+ <div class="w-full min-w-0 lg:mr-2 pr-24 py-8 bg-[rgb(var(--ec-page-bg))]">
51
+ {/* Header mirrors the resource page (docs/[type]/[id]/[version]/index.astro). */}
52
+ <div class="border-b border-[rgb(var(--ec-page-border))] md:pb-3 pt-4">
53
+ <div class="flex flex-col gap-4">
54
+ <span class="text-xs md:text-sm font-semibold text-[rgb(var(--ec-accent))] capitalize">{ownerTypeLabel}</span>
55
+ <h2 id="doc-page-header" class="text-2xl md:text-4xl font-bold text-[rgb(var(--ec-page-text))]">
56
+ {data.name} Resources
57
+ </h2>
58
+ </div>
59
+ <p class="text-base pt-4 pb-1 text-[rgb(var(--ec-page-text-muted))] font-light">
60
+ {resources.length} {resources.length === 1 ? 'resource' : 'resources'} mapped to the {data.name}{' '}
61
+ {ownerTypeLabel.toLowerCase()}.
62
+ </p>
55
63
  </div>
56
64
 
57
- <div class="py-8 w-full">
65
+ <div class="pt-8 pb-4 w-full">
58
66
  <SystemResourcesTable resources={resources} client:only="react" />
59
67
  </div>
60
68
  </div>