@eventcatalog/core 3.25.6 → 3.26.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 (35) 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-2ILJMBQM.js → chunk-7CRFNX47.js} +1 -1
  6. package/dist/{chunk-53HXLUNO.js → chunk-ASC3AR2X.js} +1 -1
  7. package/dist/{chunk-ZEOK723Y.js → chunk-FQNBDDUF.js} +1 -1
  8. package/dist/{chunk-P23BMUBV.js → chunk-GCNIIIFG.js} +1 -1
  9. package/dist/{chunk-R7P4GTFQ.js → chunk-XUN32ZVJ.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +562 -19
  13. package/dist/eventcatalog.js +572 -27
  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/astro.config.mjs +2 -1
  19. package/eventcatalog/integrations/eventcatalog-features.ts +13 -0
  20. package/eventcatalog/public/icons/graphql.svg +3 -1
  21. package/eventcatalog/src/components/FieldsExplorer/FieldFilters.tsx +225 -0
  22. package/eventcatalog/src/components/FieldsExplorer/FieldNodeGraph.tsx +521 -0
  23. package/eventcatalog/src/components/FieldsExplorer/FieldsExplorer.tsx +501 -0
  24. package/eventcatalog/src/components/FieldsExplorer/FieldsTable.tsx +236 -0
  25. package/eventcatalog/src/enterprise/fields/field-extractor.test.ts +241 -0
  26. package/eventcatalog/src/enterprise/fields/field-extractor.ts +183 -0
  27. package/eventcatalog/src/enterprise/fields/field-indexer.ts +131 -0
  28. package/eventcatalog/src/enterprise/fields/fields-db.test.ts +186 -0
  29. package/eventcatalog/src/enterprise/fields/fields-db.ts +453 -0
  30. package/eventcatalog/src/enterprise/fields/pages/api/fields.ts +43 -0
  31. package/eventcatalog/src/enterprise/fields/pages/fields.astro +19 -0
  32. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +23 -3
  33. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/graphql/[filename].astro +14 -16
  34. package/eventcatalog/src/utils/node-graphs/field-node-graph.ts +192 -0
  35. package/package.json +4 -2
package/dist/generate.cjs CHANGED
@@ -78,7 +78,7 @@ var getEventCatalogConfigFile = async (projectDirectory) => {
78
78
  var import_picocolors = __toESM(require("picocolors"), 1);
79
79
 
80
80
  // package.json
81
- var version = "3.25.6";
81
+ var version = "3.26.0";
82
82
 
83
83
  // src/constants.ts
84
84
  var VERSION = version;
package/dist/generate.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generate
3
- } from "./chunk-53HXLUNO.js";
4
- import "./chunk-R7P4GTFQ.js";
5
- import "./chunk-P23BMUBV.js";
3
+ } from "./chunk-ASC3AR2X.js";
4
+ import "./chunk-XUN32ZVJ.js";
5
+ import "./chunk-GCNIIIFG.js";
6
6
  import "./chunk-5T63CXKU.js";
7
7
  export {
8
8
  generate
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(cli_logger_exports);
36
36
  var import_picocolors = __toESM(require("picocolors"), 1);
37
37
 
38
38
  // package.json
39
- var version = "3.25.6";
39
+ var version = "3.26.0";
40
40
 
41
41
  // src/constants.ts
42
42
  var VERSION = version;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  logger
3
- } from "../chunk-R7P4GTFQ.js";
4
- import "../chunk-P23BMUBV.js";
3
+ } from "../chunk-XUN32ZVJ.js";
4
+ import "../chunk-GCNIIIFG.js";
5
5
  export {
6
6
  logger
7
7
  };
@@ -149,9 +149,10 @@ export default defineConfig({
149
149
  }
150
150
  },
151
151
  ssr: {
152
- external: ['eventcatalog.auth.js', 'eventcatalog.chat.js'],
152
+ external: ['eventcatalog.auth.js', 'eventcatalog.chat.js', 'better-sqlite3'],
153
153
  },
154
154
  optimizeDeps: {
155
+ exclude: ['better-sqlite3'],
155
156
  // Pre-bundle heavy dependencies so Vite doesn't discover and transform
156
157
  // them lazily on first request. This significantly reduces initial page
157
158
  // load time in dev mode.
@@ -11,6 +11,7 @@ import {
11
11
  isDevMode,
12
12
  isIntegrationsEnabled,
13
13
  isExportPDFEnabled,
14
+ isSSR,
14
15
  } from '../src/utils/feature';
15
16
 
16
17
  const catalogDirectory = process.env.CATALOG_DIR || process.cwd();
@@ -94,6 +95,18 @@ export default function eventCatalogIntegration(): AstroIntegration {
94
95
  });
95
96
  }
96
97
 
98
+ // Fields Explorer (requires SSR — pages live outside src/pages to avoid static-mode auto-discovery)
99
+ if (isSSR()) {
100
+ params.injectRoute({
101
+ pattern: '/schemas/fields',
102
+ entrypoint: path.join(catalogDirectory, 'src/enterprise/fields/pages/fields.astro'),
103
+ });
104
+ params.injectRoute({
105
+ pattern: '/api/schemas/fields',
106
+ entrypoint: path.join(catalogDirectory, 'src/enterprise/fields/pages/api/fields.ts'),
107
+ });
108
+ }
109
+
97
110
  // Warn if integrations are configured without Scale plan
98
111
  if (config.integrations && !isIntegrationsEnabled()) {
99
112
  console.warn('[EventCatalog] Integrations require the Scale plan. Analytics integrations will not be loaded.');
@@ -1 +1,3 @@
1
- <svg viewBox="-16 0 288 288" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g fill-rule="evenodd"> <path d="M152.575995,32.9634453 L211.722058,67.1124427 C213.387503,65.348329 215.332859,63.7907081 217.539734,62.5088708 C229.806457,55.4573413 245.452038,59.6441869 252.577021,71.9109101 C259.62855,84.1776333 255.441705,99.8232143 243.174982,106.948197 C240.984974,108.207124 238.684929,109.10784 236.343812,109.66832 L236.343812,177.99302 C238.659582,178.554683 240.934429,179.449686 243.101528,180.695443 C255.441705,187.820426 259.62855,203.466007 252.503568,215.73273 C245.452038,227.999453 229.733004,232.186299 217.46628,225.13477 C215.024105,223.730884 212.902189,221.983449 211.125456,219.988103 L152.340162,253.928803 C153.180519,256.46808 153.635248,259.18431 153.635248,262.008393 C153.635248,276.111452 142.176512,287.64364 128,287.64364 C113.823488,287.64364 102.364752,276.184905 102.364752,262.008393 C102.364752,259.488481 102.726802,257.054441 103.40181,254.755362 L44.2714887,220.615454 C42.6238257,222.34493 40.705394,223.873378 38.5337196,225.13477 C26.193543,232.186299 10.547962,227.999453 3.49643248,215.73273 C-3.55509701,203.466007 0.631748621,187.820426 12.8984718,180.695443 C15.0673397,179.448669 17.3418356,178.553222 19.6561876,177.991646 L19.6561876,109.66832 C17.3150714,109.10784 15.0150257,108.207124 12.8250184,106.948197 C0.558295189,99.8966677 -3.62855044,84.1776333 3.42297904,71.9109101 C10.4745085,59.6441869 26.193543,55.4573413 38.4602662,62.5088708 C40.6551374,63.7837361 42.5913269,65.3313931 44.2507413,67.0836756 L103.41276,32.9254664 C102.730718,30.6154532 102.364752,28.1687503 102.364752,25.6352478 C102.364752,11.4587354 113.823488,0 128,0 C142.176512,0 153.635248,11.4587354 153.635248,25.6352478 C153.635248,28.1826393 153.265258,30.6422768 152.575995,32.9634453 Z M146.413638,43.4848713 L205.700555,77.715193 C203.867899,84.1516888 204.540515,91.2885638 208.137694,97.5461579 C211.746601,103.824151 217.625783,107.985785 224.150543,109.607654 L224.150543,178.017842 C223.818032,178.099463 223.48718,178.187674 223.158201,178.282419 L145.72529,44.1686182 C145.959017,43.9450469 146.18851,43.7170876 146.413638,43.4848713 Z M110.29093,44.1841203 L32.8593279,178.295656 C32.524559,178.19844 32.1878611,178.108015 31.8494573,178.024438 L31.8494573,109.607654 C38.3742168,107.985785 44.2533992,103.824151 47.8623055,97.5461579 C51.466599,91.276189 52.1347497,84.1234583 50.2885426,77.6770157 L109.558747,43.4563434 C109.797913,43.7038019 110.042027,43.9464469 110.29093,44.1841203 Z M135.162749,50.259763 L212.576817,184.340928 C210.844241,185.99279 209.317376,187.91755 208.064241,190.097482 C206.818484,192.264581 205.923481,194.539429 205.361818,196.855198 L50.6395564,196.855198 C50.0779798,194.540846 49.1825333,192.26635 47.9357589,190.097482 C46.6776713,187.931497 45.1539248,186.017428 43.4300297,184.372667 L120.858821,50.2659993 C123.124941,50.9200822 125.520915,51.2704957 128,51.2704957 C130.486952,51.2704957 132.890265,50.9178547 135.162749,50.259763 Z M146.958084,244.737995 L205.860107,210.729899 C205.683398,210.174924 205.525483,209.614096 205.38664,209.048468 L50.6067643,209.048468 C50.5246888,209.380792 50.4360084,209.711472 50.3407792,210.040295 L109.531782,244.215239 C114.192298,239.378545 120.739739,236.373145 128,236.373145 C135.518379,236.373145 142.272352,239.596041 146.958084,244.737995 Z" fill="#E535AB"> </path> </g> </g></svg>
1
+ <svg viewBox="-16 0 288 288" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
2
+ <path d="M152.575995,32.9634453 L211.722058,67.1124427 C213.387503,65.348329 215.332859,63.7907081 217.539734,62.5088708 C229.806457,55.4573413 245.452038,59.6441869 252.577021,71.9109101 C259.62855,84.1776333 255.441705,99.8232143 243.174982,106.948197 C240.984974,108.207124 238.684929,109.10784 236.343812,109.66832 L236.343812,177.99302 C238.659582,178.554683 240.934429,179.449686 243.101528,180.695443 C255.441705,187.820426 259.62855,203.466007 252.503568,215.73273 C245.452038,227.999453 229.733004,232.186299 217.46628,225.13477 C215.024105,223.730884 212.902189,221.983449 211.125456,219.988103 L152.340162,253.928803 C153.180519,256.46808 153.635248,259.18431 153.635248,262.008393 C153.635248,276.111452 142.176512,287.64364 128,287.64364 C113.823488,287.64364 102.364752,276.184905 102.364752,262.008393 C102.364752,259.488481 102.726802,257.054441 103.40181,254.755362 L44.2714887,220.615454 C42.6238257,222.34493 40.705394,223.873378 38.5337196,225.13477 C26.193543,232.186299 10.547962,227.999453 3.49643248,215.73273 C-3.55509701,203.466007 0.631748621,187.820426 12.8984718,180.695443 C15.0673397,179.448669 17.3418356,178.553222 19.6561876,177.991646 L19.6561876,109.66832 C17.3150714,109.10784 15.0150257,108.207124 12.8250184,106.948197 C0.558295189,99.8966677 -3.62855044,84.1776333 3.42297904,71.9109101 C10.4745085,59.6441869 26.193543,55.4573413 38.4602662,62.5088708 C40.6551374,63.7837361 42.5913269,65.3313931 44.2507413,67.0836756 L103.41276,32.9254664 C102.730718,30.6154532 102.364752,28.1687503 102.364752,25.6352478 C102.364752,11.4587354 113.823488,0 128,0 C142.176512,0 153.635248,11.4587354 153.635248,25.6352478 C153.635248,28.1826393 153.265258,30.6422768 152.575995,32.9634453 Z M146.413638,43.4848713 L205.700555,77.715193 C203.867899,84.1516888 204.540515,91.2885638 208.137694,97.5461579 C211.746601,103.824151 217.625783,107.985785 224.150543,109.607654 L224.150543,178.017842 C223.818032,178.099463 223.48718,178.187674 223.158201,178.282419 L145.72529,44.1686182 C145.959017,43.9450469 146.18851,43.7170876 146.413638,43.4848713 Z M110.29093,44.1841203 L32.8593279,178.295656 C32.524559,178.19844 32.1878611,178.108015 31.8494573,178.024438 L31.8494573,109.607654 C38.3742168,107.985785 44.2533992,103.824151 47.8623055,97.5461579 C51.466599,91.276189 52.1347497,84.1234583 50.2885426,77.6770157 L109.558747,43.4563434 C109.797913,43.7038019 110.042027,43.9464469 110.29093,44.1841203 Z M135.162749,50.259763 L212.576817,184.340928 C210.844241,185.99279 209.317376,187.91755 208.064241,190.097482 C206.818484,192.264581 205.923481,194.539429 205.361818,196.855198 L50.6395564,196.855198 C50.0779798,194.540846 49.1825333,192.26635 47.9357589,190.097482 C46.6776713,187.931497 45.1539248,186.017428 43.4300297,184.372667 L120.858821,50.2659993 C123.124941,50.9200822 125.520915,51.2704957 128,51.2704957 C130.486952,51.2704957 132.890265,50.9178547 135.162749,50.259763 Z M146.958084,244.737995 L205.860107,210.729899 C205.683398,210.174924 205.525483,209.614096 205.38664,209.048468 L50.6067643,209.048468 C50.5246888,209.380792 50.4360084,209.711472 50.3407792,210.040295 L109.531782,244.215239 C114.192298,239.378545 120.739739,236.373145 128,236.373145 C135.518379,236.373145 142.272352,239.596041 146.958084,244.737995 Z" fill="#E535AB" fill-rule="evenodd"/>
3
+ </svg>
@@ -0,0 +1,225 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Search, X, Filter } from 'lucide-react';
3
+ import { FilterDropdown, CheckboxItem } from '../Tables/Discover/FilterComponents';
4
+
5
+ export interface FieldFiltersProps {
6
+ searchQuery: string;
7
+ onSearchChange: (value: string) => void;
8
+ selectedFormats: string[];
9
+ onFormatsChange: (values: string[]) => void;
10
+ selectedMessageTypes: string[];
11
+ onMessageTypesChange: (values: string[]) => void;
12
+ sharedOnly: boolean;
13
+ onSharedOnlyChange: (value: boolean) => void;
14
+ conflictingOnly: boolean;
15
+ onConflictingOnlyChange: (value: boolean) => void;
16
+ facets: {
17
+ formats: { value: string; count: number }[];
18
+ messageTypes: { value: string; count: number }[];
19
+ } | null;
20
+ isScaleEnabled?: boolean;
21
+ }
22
+
23
+ export default function FieldFilters({
24
+ searchQuery,
25
+ onSearchChange,
26
+ selectedFormats,
27
+ onFormatsChange,
28
+ selectedMessageTypes,
29
+ onMessageTypesChange,
30
+ sharedOnly,
31
+ onSharedOnlyChange,
32
+ conflictingOnly,
33
+ onConflictingOnlyChange,
34
+ facets,
35
+ isScaleEnabled = false,
36
+ }: FieldFiltersProps) {
37
+ const [localSearch, setLocalSearch] = useState(searchQuery);
38
+
39
+ // Sync external changes
40
+ useEffect(() => {
41
+ setLocalSearch(searchQuery);
42
+ }, [searchQuery]);
43
+
44
+ // Debounce search input
45
+ useEffect(() => {
46
+ const timeout = setTimeout(() => {
47
+ onSearchChange(localSearch);
48
+ }, 500);
49
+ return () => clearTimeout(timeout);
50
+ }, [localSearch]);
51
+
52
+ const toggleFormat = (value: string) => {
53
+ if (selectedFormats.includes(value)) {
54
+ onFormatsChange(selectedFormats.filter((f) => f !== value));
55
+ } else {
56
+ onFormatsChange([...selectedFormats, value]);
57
+ }
58
+ };
59
+
60
+ const toggleMessageType = (value: string) => {
61
+ if (selectedMessageTypes.includes(value)) {
62
+ onMessageTypesChange(selectedMessageTypes.filter((t) => t !== value));
63
+ } else {
64
+ onMessageTypesChange([...selectedMessageTypes, value]);
65
+ }
66
+ };
67
+
68
+ const activeFilterCount =
69
+ selectedFormats.length + selectedMessageTypes.length + (sharedOnly ? 1 : 0) + (conflictingOnly ? 1 : 0);
70
+
71
+ const clearAllFilters = () => {
72
+ onFormatsChange([]);
73
+ onMessageTypesChange([]);
74
+ onSharedOnlyChange(false);
75
+ onConflictingOnlyChange(false);
76
+ setLocalSearch('');
77
+ onSearchChange('');
78
+ };
79
+
80
+ return (
81
+ <div className="space-y-4">
82
+ {/* Search Input */}
83
+ <div className="relative">
84
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[rgb(var(--ec-icon-color))]" />
85
+ <input
86
+ type="text"
87
+ value={localSearch}
88
+ onChange={(e) => setLocalSearch(e.target.value)}
89
+ placeholder="Search fields..."
90
+ className="w-full pl-9 pr-8 py-2 text-sm bg-[rgb(var(--ec-dropdown-bg))] text-[rgb(var(--ec-input-text))] border border-[rgb(var(--ec-dropdown-border))] rounded-lg placeholder:text-[rgb(var(--ec-icon-color))] focus:outline-hidden focus:ring-1 focus:ring-[rgb(var(--ec-accent)/0.3)] focus:border-[rgb(var(--ec-accent))] transition-colors"
91
+ />
92
+ {localSearch && (
93
+ <button
94
+ onClick={() => {
95
+ setLocalSearch('');
96
+ onSearchChange('');
97
+ }}
98
+ className="absolute right-2 top-1/2 -translate-y-1/2 p-0.5 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-page-text))]"
99
+ >
100
+ <X className="w-3.5 h-3.5" />
101
+ </button>
102
+ )}
103
+ </div>
104
+
105
+ {/* Filter Section Header */}
106
+ <div className="flex items-center gap-2">
107
+ <Filter className="w-3.5 h-3.5 text-[rgb(var(--ec-icon-color))]" />
108
+ <h3 className="text-[11px] font-bold uppercase tracking-widest text-[rgb(var(--ec-page-text))]">Filters</h3>
109
+ </div>
110
+
111
+ {/* Schema Format Filter */}
112
+ {facets && facets.formats.length > 0 && (
113
+ <div>
114
+ <label className="block text-xs font-medium text-[rgb(var(--ec-page-text)/0.8)] mb-1.5">Schema Format</label>
115
+ <FilterDropdown
116
+ label="Select formats..."
117
+ selectedItems={selectedFormats}
118
+ onClear={() => onFormatsChange([])}
119
+ onRemoveItem={(item) => toggleFormat(item)}
120
+ >
121
+ {facets.formats.map((format) => (
122
+ <CheckboxItem
123
+ key={format.value}
124
+ label={format.value.charAt(0).toUpperCase() + format.value.slice(1)}
125
+ checked={selectedFormats.includes(format.value)}
126
+ onChange={() => toggleFormat(format.value)}
127
+ count={format.count}
128
+ />
129
+ ))}
130
+ </FilterDropdown>
131
+ </div>
132
+ )}
133
+
134
+ {/* Message Type Filter */}
135
+ {facets && facets.messageTypes.length > 0 && (
136
+ <div>
137
+ <label className="block text-xs font-medium text-[rgb(var(--ec-page-text)/0.8)] mb-1.5">Message Type</label>
138
+ <FilterDropdown
139
+ label="Select message types..."
140
+ selectedItems={selectedMessageTypes}
141
+ onClear={() => onMessageTypesChange([])}
142
+ onRemoveItem={(item) => toggleMessageType(item)}
143
+ >
144
+ {facets.messageTypes.map((msgType) => (
145
+ <CheckboxItem
146
+ key={msgType.value}
147
+ label={msgType.value.charAt(0).toUpperCase() + msgType.value.slice(1)}
148
+ checked={selectedMessageTypes.includes(msgType.value)}
149
+ onChange={() => toggleMessageType(msgType.value)}
150
+ count={msgType.count}
151
+ />
152
+ ))}
153
+ </FilterDropdown>
154
+ </div>
155
+ )}
156
+
157
+ {/* Shared Fields Only Toggle */}
158
+ <div>
159
+ <label className="flex items-center gap-2 cursor-pointer group">
160
+ <div
161
+ className={`w-4 h-4 flex-shrink-0 rounded border flex items-center justify-center transition-colors ${
162
+ sharedOnly
163
+ ? 'bg-[rgb(var(--ec-accent))] border-[rgb(var(--ec-accent))]'
164
+ : 'border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-input-bg))] group-hover:border-[rgb(var(--ec-icon-color))]'
165
+ }`}
166
+ onClick={() => onSharedOnlyChange(!sharedOnly)}
167
+ >
168
+ {sharedOnly && (
169
+ <svg className="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
170
+ <path d="M5 13l4 4L19 7" strokeLinecap="round" strokeLinejoin="round" />
171
+ </svg>
172
+ )}
173
+ </div>
174
+ <span className="text-sm text-[rgb(var(--ec-page-text))]" onClick={() => onSharedOnlyChange(!sharedOnly)}>
175
+ Shared fields only
176
+ </span>
177
+ </label>
178
+ <p className="text-[11px] text-[rgb(var(--ec-page-text-muted))] mt-1 ml-6">
179
+ Show only fields that appear in multiple messages
180
+ </p>
181
+ </div>
182
+
183
+ {/* Conflicting Fields Only Toggle */}
184
+ {isScaleEnabled && (
185
+ <div>
186
+ <label className="flex items-center gap-2 cursor-pointer group">
187
+ <div
188
+ className={`w-4 h-4 flex-shrink-0 rounded border flex items-center justify-center transition-colors ${
189
+ conflictingOnly
190
+ ? 'bg-amber-500 border-amber-500'
191
+ : 'border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-input-bg))] group-hover:border-[rgb(var(--ec-icon-color))]'
192
+ }`}
193
+ onClick={() => onConflictingOnlyChange(!conflictingOnly)}
194
+ >
195
+ {conflictingOnly && (
196
+ <svg className="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
197
+ <path d="M5 13l4 4L19 7" strokeLinecap="round" strokeLinejoin="round" />
198
+ </svg>
199
+ )}
200
+ </div>
201
+ <span className="text-sm text-[rgb(var(--ec-page-text))]" onClick={() => onConflictingOnlyChange(!conflictingOnly)}>
202
+ Conflicting fields
203
+ </span>
204
+ </label>
205
+ <p className="text-[11px] text-[rgb(var(--ec-page-text-muted))] mt-1 ml-6">
206
+ Show only fields with inconsistent types across messages
207
+ </p>
208
+ </div>
209
+ )}
210
+
211
+ {/* Results & Clear */}
212
+ {activeFilterCount > 0 && (
213
+ <div className="flex items-center justify-between pt-3 mt-2 border-t border-[rgb(var(--ec-page-border))]">
214
+ <span className="text-xs text-[rgb(var(--ec-page-text-muted))]">
215
+ <span className="font-semibold text-[rgb(var(--ec-page-text))]">{activeFilterCount}</span> active{' '}
216
+ {activeFilterCount === 1 ? 'filter' : 'filters'}
217
+ </span>
218
+ <button onClick={clearAllFilters} className="text-xs font-medium text-[rgb(var(--ec-accent))] hover:underline">
219
+ Clear all
220
+ </button>
221
+ </div>
222
+ )}
223
+ </div>
224
+ );
225
+ }