@apiboost/omnispec 1.1.0-next.3 → 1.1.0-next.5

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 (59) hide show
  1. package/dist/cjs/asyncapi/index.js +2 -2
  2. package/dist/cjs/chunks/{AsyncApiSpec-BzgUioEK.js → AsyncApiSpec-B-RGmOUL.js} +345 -122
  3. package/dist/cjs/chunks/AsyncApiSpec-B-RGmOUL.js.map +1 -0
  4. package/dist/cjs/chunks/{Collapsible-BTWyDBkX.js → Collapsible-qjYtxwP0.js} +17 -6
  5. package/dist/cjs/chunks/Collapsible-qjYtxwP0.js.map +1 -0
  6. package/dist/cjs/chunks/{OmniSpecRenderer-CRipDRtC.js → OmniSpecRenderer-Bh-IQod3.js} +3 -3
  7. package/dist/cjs/chunks/{OmniSpecRenderer-CRipDRtC.js.map → OmniSpecRenderer-Bh-IQod3.js.map} +1 -1
  8. package/dist/cjs/chunks/{OpenApiSpec-BOlesoUX.js → OpenApiSpec-5V9E-wQH.js} +3 -3
  9. package/dist/cjs/chunks/{OpenApiSpec-BOlesoUX.js.map → OpenApiSpec-5V9E-wQH.js.map} +1 -1
  10. package/dist/cjs/chunks/{OpenIdConnectManual-Bh7tdxoH.js → OpenIdConnectManual-DXDG8bcp.js} +2 -2
  11. package/dist/cjs/chunks/{OpenIdConnectManual-Bh7tdxoH.js.map → OpenIdConnectManual-DXDG8bcp.js.map} +1 -1
  12. package/dist/cjs/core/public.js +2 -2
  13. package/dist/cjs/index.js +5 -5
  14. package/dist/cjs/openapi/index.js +3 -3
  15. package/dist/cjs/wc/index.js +1 -1
  16. package/dist/esm/asyncapi/index.js +2 -2
  17. package/dist/esm/chunks/{AsyncApiSpec-B757IIHz.js → AsyncApiSpec-C0tdvyEP.js} +345 -122
  18. package/dist/esm/chunks/AsyncApiSpec-C0tdvyEP.js.map +1 -0
  19. package/dist/esm/chunks/{Collapsible-MyJB2agz.js → Collapsible-CPxS90KX.js} +17 -6
  20. package/dist/esm/chunks/Collapsible-CPxS90KX.js.map +1 -0
  21. package/dist/esm/chunks/{OmniSpecRenderer-5RUZx-C7.js → OmniSpecRenderer-DmXZtPRr.js} +3 -3
  22. package/dist/esm/chunks/{OmniSpecRenderer-5RUZx-C7.js.map → OmniSpecRenderer-DmXZtPRr.js.map} +1 -1
  23. package/dist/esm/chunks/{OpenApiSpec-wm3tH2vF.js → OpenApiSpec-CoPhPro1.js} +3 -3
  24. package/dist/esm/chunks/{OpenApiSpec-wm3tH2vF.js.map → OpenApiSpec-CoPhPro1.js.map} +1 -1
  25. package/dist/esm/chunks/{OpenIdConnectManual-DPmTIIIy.js → OpenIdConnectManual-DhrAcSCa.js} +2 -2
  26. package/dist/esm/chunks/{OpenIdConnectManual-DPmTIIIy.js.map → OpenIdConnectManual-DhrAcSCa.js.map} +1 -1
  27. package/dist/esm/core/public.js +2 -2
  28. package/dist/esm/index.js +5 -5
  29. package/dist/esm/openapi/index.js +3 -3
  30. package/dist/esm/wc/index.js +1 -1
  31. package/dist/types/asyncapi/AsyncApiSpec.d.ts.map +1 -1
  32. package/dist/types/asyncapi/components/AsyncOverview.d.ts.map +1 -1
  33. package/dist/types/asyncapi/components/BindingsSection.d.ts.map +1 -1
  34. package/dist/types/asyncapi/components/ChannelDetail.d.ts.map +1 -1
  35. package/dist/types/asyncapi/components/ComponentsSection.d.ts.map +1 -1
  36. package/dist/types/asyncapi/components/ExternalDocsLink.d.ts.map +1 -1
  37. package/dist/types/asyncapi/components/ExternalLink.d.ts +31 -0
  38. package/dist/types/asyncapi/components/ExternalLink.d.ts.map +1 -0
  39. package/dist/types/asyncapi/components/MessagePayload.d.ts.map +1 -1
  40. package/dist/types/asyncapi/components/SecurityRequirementBadges.d.ts.map +1 -1
  41. package/dist/types/asyncapi/components/TagChips.d.ts +27 -0
  42. package/dist/types/asyncapi/components/TagChips.d.ts.map +1 -0
  43. package/dist/types/asyncapi/components/field-display.d.ts +11 -3
  44. package/dist/types/asyncapi/components/field-display.d.ts.map +1 -1
  45. package/dist/types/asyncapi/parser/asyncapi-parser.d.ts.map +1 -1
  46. package/dist/types/asyncapi/tag-grouping.d.ts +9 -5
  47. package/dist/types/asyncapi/tag-grouping.d.ts.map +1 -1
  48. package/dist/types/asyncapi/types/asyncapi.types.d.ts +25 -12
  49. package/dist/types/asyncapi/types/asyncapi.types.d.ts.map +1 -1
  50. package/dist/types/core/components/Navigation/SearchBar.d.ts +7 -1
  51. package/dist/types/core/components/Navigation/SearchBar.d.ts.map +1 -1
  52. package/dist/types/core/components/SchemaViewer/schema-utils.d.ts.map +1 -1
  53. package/dist/wc/standalone.js +1 -1
  54. package/dist/wc/standalone.js.map +1 -1
  55. package/package.json +12 -2
  56. package/dist/cjs/chunks/AsyncApiSpec-BzgUioEK.js.map +0 -1
  57. package/dist/cjs/chunks/Collapsible-BTWyDBkX.js.map +0 -1
  58. package/dist/esm/chunks/AsyncApiSpec-B757IIHz.js.map +0 -1
  59. package/dist/esm/chunks/Collapsible-MyJB2agz.js.map +0 -1
@@ -4,7 +4,7 @@ var jsxRuntime = require('react/jsx-runtime');
4
4
  var React = require('react');
5
5
  var css = require('@emotion/css');
6
6
  var useSpecFetcher = require('./useSpecFetcher-BAbHnXax.js');
7
- var Collapsible = require('./Collapsible-BTWyDBkX.js');
7
+ var Collapsible = require('./Collapsible-qjYtxwP0.js');
8
8
  var ErrorBoundary = require('./ErrorBoundary-CezYgQDG.js');
9
9
  var refResolver = require('./ref-resolver-0A8GNRI3.js');
10
10
  require('@emotion/styled');
@@ -41,6 +41,16 @@ function mergeMessageLayer(base, override) {
41
41
  }
42
42
  return result;
43
43
  }
44
+ function deriveAppliedTraitNames(traits, rawTraits) {
45
+ return traits.map((trait, i) => {
46
+ const raw = rawTraits?.[i];
47
+ if (raw && typeof raw === "object" && typeof raw.$ref === "string") {
48
+ const last = raw.$ref.split("/").pop();
49
+ if (last) return refResolver.decodeJsonPointerSegment(last);
50
+ }
51
+ return trait.name ?? trait.title ?? `trait ${i + 1}`;
52
+ });
53
+ }
44
54
  function applyMessageTraits(msg, isV3) {
45
55
  if (!msg.traits || msg.traits.length === 0) return msg;
46
56
  if (isV3) {
@@ -84,7 +94,7 @@ async function parseAsyncApiSpec(specInput) {
84
94
  const rawApi = raw;
85
95
  const defaultContentType = api.defaultContentType;
86
96
  const channels = isV3 ? extractChannelsV3(api, rawApi, defaultContentType) : extractChannelsV2(api, defaultContentType);
87
- const components = extractComponents(api, isV3, defaultContentType);
97
+ const components = extractComponents(api, rawApi, isV3, defaultContentType);
88
98
  const root = api;
89
99
  const tags = api.tags?.map((tag) => {
90
100
  const raw2 = tag;
@@ -199,11 +209,20 @@ function resolveChannelName(rawChannel, resolvedChannel, addressToName) {
199
209
  }
200
210
  return void 0;
201
211
  }
212
+ function normalizeReplyAddress(address) {
213
+ if (typeof address === "string") {
214
+ return address.length > 0 ? { value: address } : void 0;
215
+ }
216
+ if (address && typeof address === "object") {
217
+ const { location, description } = address;
218
+ if (location || description) return { location, description };
219
+ }
220
+ return void 0;
221
+ }
202
222
  function extractChannelsV3(api, rawApi, defaultContentType) {
203
- if (!api.channels) return [];
204
223
  const channelMap = /* @__PURE__ */ new Map();
205
224
  const addressToName = /* @__PURE__ */ new Map();
206
- for (const [channelName, ch] of Object.entries(api.channels)) {
225
+ for (const [channelName, ch] of Object.entries(api.channels ?? {})) {
207
226
  const raw = ch;
208
227
  const address = ch.address ?? channelName;
209
228
  addressToName.set(address, channelName);
@@ -247,9 +266,10 @@ function extractChannelsV3(api, rawApi, defaultContentType) {
247
266
  addressToName
248
267
  );
249
268
  const raw = op;
250
- const reply = replyMessages.length > 0 || op.reply?.address || replyChannelName ? {
269
+ const replyAddress = normalizeReplyAddress(op.reply?.address);
270
+ const reply = replyMessages.length > 0 || replyAddress || replyChannelName ? {
251
271
  messages: replyMessages,
252
- address: op.reply?.address,
272
+ address: replyAddress,
253
273
  channelName: replyChannelName
254
274
  } : void 0;
255
275
  const operation = {
@@ -283,16 +303,17 @@ function extractChannelsV3(api, rawApi, defaultContentType) {
283
303
  channels.push({
284
304
  name: "__unassigned__",
285
305
  address: "Unassigned operations",
286
- description: "These operations reference a channel that is not defined in this document.",
306
+ description: "Unresolved reference: these operations reference an undefined channel (the channel is missing from this document, or the document defines no channels). They are surfaced here rather than dropped.",
287
307
  operations: orphanOperations
288
308
  });
289
309
  }
290
310
  return channels;
291
311
  }
292
- function convertMessage(rawMsg, isV3, defaultContentType) {
312
+ function convertMessage(rawMsg, isV3, defaultContentType, rawTraits) {
293
313
  const msg = applyMessageTraits(rawMsg, isV3);
294
314
  const hasOwnContentType = typeof msg.contentType === "string" && msg.contentType.length > 0;
295
315
  const contentType = hasOwnContentType ? msg.contentType : defaultContentType ?? "application/json";
316
+ const appliedTraits = rawMsg.traits && rawMsg.traits.length > 0 ? deriveAppliedTraitNames(rawMsg.traits, rawTraits) : void 0;
296
317
  return {
297
318
  name: msg.name,
298
319
  title: msg.title,
@@ -307,15 +328,17 @@ function convertMessage(rawMsg, isV3, defaultContentType) {
307
328
  tags: msg.tags,
308
329
  examples: msg.examples,
309
330
  bindings: msg.bindings,
310
- externalDocs: msg.externalDocs
331
+ externalDocs: msg.externalDocs,
332
+ appliedTraits
311
333
  };
312
334
  }
313
- function extractComponents(api, isV3, defaultContentType) {
335
+ function extractComponents(api, rawApi, isV3, defaultContentType) {
314
336
  const components = api.components;
315
337
  const rawMessages = api.components?.messages ?? {};
338
+ const rawComponentMessages = rawApi.components?.messages ?? {};
316
339
  const messages = {};
317
340
  for (const [name, msg] of Object.entries(rawMessages)) {
318
- messages[name] = convertMessage(msg, isV3, defaultContentType);
341
+ messages[name] = convertMessage(msg, isV3, defaultContentType, rawComponentMessages[name]?.traits);
319
342
  }
320
343
  return {
321
344
  schemas: api.components?.schemas ?? {},
@@ -396,19 +419,17 @@ function useAsyncApiSpec(spec) {
396
419
  return useSpecFetcher.useSpecFetcher(spec, parser);
397
420
  }
398
421
 
399
- function ExternalDocsLink({ externalDocs, context, className }) {
400
- const label = externalDocs.description ?? "External docs";
401
- const ariaLabel = `${context ? `${context} external docs` : "External docs"}, opens in new tab`;
422
+ function ExternalLink({ href, children, label, className }) {
402
423
  return /* @__PURE__ */ jsxRuntime.jsxs(
403
424
  "a",
404
425
  {
405
- href: externalDocs.url,
426
+ href,
406
427
  target: "_blank",
407
428
  rel: "noopener noreferrer",
408
- "aria-label": ariaLabel,
429
+ "aria-label": `${label}, opens in new tab`,
409
430
  className: css.cx(linkStyle$1, className),
410
431
  children: [
411
- label,
432
+ children,
412
433
  /* @__PURE__ */ jsxRuntime.jsx(useSpecFetcher.Icon, { name: "external-link", size: "0.875em" })
413
434
  ]
414
435
  }
@@ -418,7 +439,6 @@ const linkStyle$1 = css.css({
418
439
  display: "inline-flex",
419
440
  alignItems: "center",
420
441
  gap: "0.25rem",
421
- fontSize: "var(--omnispec-font-size-xs)",
422
442
  color: "var(--omnispec-fg-link)",
423
443
  textDecoration: "none",
424
444
  minWidth: 0,
@@ -428,6 +448,15 @@ const linkStyle$1 = css.css({
428
448
  }
429
449
  });
430
450
 
451
+ function ExternalDocsLink({ externalDocs, context, className }) {
452
+ const label = externalDocs.description ?? "External docs";
453
+ const ariaLabel = context ? `${context} external docs` : "External docs";
454
+ return /* @__PURE__ */ jsxRuntime.jsx(ExternalLink, { href: externalDocs.url, label: ariaLabel, className: css.cx(docsLinkStyle, className), children: label });
455
+ }
456
+ const docsLinkStyle = css.css({
457
+ fontSize: "var(--omnispec-font-size-xs)"
458
+ });
459
+
431
460
  const containerStyle$4 = css.css({
432
461
  marginBottom: "2rem",
433
462
  paddingBottom: "1.5rem",
@@ -491,12 +520,19 @@ function AsyncOverview({ spec }) {
491
520
  ] }),
492
521
  spec.contact.url && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
493
522
  " \xB7 ",
494
- /* @__PURE__ */ jsxRuntime.jsx("a", { href: spec.contact.url, target: "_blank", rel: "noopener noreferrer", className: linkStyle, children: spec.contact.url })
523
+ /* @__PURE__ */ jsxRuntime.jsx(
524
+ ExternalLink,
525
+ {
526
+ href: spec.contact.url,
527
+ label: `${spec.contact.name ?? spec.title ?? "Contact"} website`,
528
+ children: spec.contact.url
529
+ }
530
+ )
495
531
  ] })
496
532
  ] }),
497
533
  spec.license && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: metaStyle, children: [
498
534
  "License: ",
499
- spec.license.url ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: spec.license.url, target: "_blank", rel: "noopener noreferrer", className: linkStyle, children: spec.license.name }) : spec.license.name
535
+ spec.license.url ? /* @__PURE__ */ jsxRuntime.jsx(ExternalLink, { href: spec.license.url, label: `${spec.license.name} license`, children: spec.license.name }) : spec.license.name
500
536
  ] }),
501
537
  spec.externalDocs && /* @__PURE__ */ jsxRuntime.jsx("div", { className: metaStyle, children: /* @__PURE__ */ jsxRuntime.jsx(ExternalDocsLink, { externalDocs: spec.externalDocs, context: spec.title ?? "API" }) })
502
538
  ] });
@@ -576,7 +612,7 @@ function isScalarArray(value) {
576
612
  (item) => item === null || typeof item !== "object"
577
613
  );
578
614
  }
579
- function renderValue(value) {
615
+ function renderValue(value, verbatim) {
580
616
  if (value === null) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: mutedStyle, children: "null" });
581
617
  if (typeof value === "boolean") return /* @__PURE__ */ jsxRuntime.jsx("code", { className: codeStyle, children: value ? "true" : "false" });
582
618
  if (typeof value === "number") return /* @__PURE__ */ jsxRuntime.jsx("code", { className: codeStyle, children: String(value) });
@@ -594,7 +630,7 @@ function renderValue(value) {
594
630
  }
595
631
  const nested = Object.entries(value).filter(([, v]) => v !== void 0);
596
632
  if (nested.length === 0) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: mutedStyle, children: "none" });
597
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: nestedStyle, children: /* @__PURE__ */ jsxRuntime.jsx(FieldRows, { fields: nested }) });
633
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: nestedStyle, children: /* @__PURE__ */ jsxRuntime.jsx(FieldRows, { fields: nested, verbatim }) });
598
634
  }
599
635
  return /* @__PURE__ */ jsxRuntime.jsx("code", { className: jsonStyle, children: JSON.stringify(value) });
600
636
  }
@@ -622,6 +658,9 @@ function SchemaLeaf({ schema }) {
622
658
  const type = typeof schema.type === "string" ? schema.type : void 0;
623
659
  const description = typeof schema.description === "string" ? schema.description : void 0;
624
660
  const enumValues = Array.isArray(schema.enum) ? schema.enum : void 0;
661
+ if (enumValues && enumValues.length === 1 && !description) {
662
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: schemaLeafStyle, children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: enumItemStyle, children: String(enumValues[0]) }) });
663
+ }
625
664
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schemaLeafStyle, children: [
626
665
  type && /* @__PURE__ */ jsxRuntime.jsx("span", { className: schemaTypeStyle, children: type }),
627
666
  description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: schemaDescStyle, children: description }),
@@ -631,11 +670,14 @@ function SchemaLeaf({ schema }) {
631
670
  ] })
632
671
  ] });
633
672
  }
634
- function FieldRows({ fields }) {
673
+ function FieldRows({
674
+ fields,
675
+ verbatim = false
676
+ }) {
635
677
  if (fields.length === 0) return null;
636
678
  return /* @__PURE__ */ jsxRuntime.jsx("dl", { className: listStyle$1, children: fields.map(([field, value]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rowStyle$1, children: [
637
- /* @__PURE__ */ jsxRuntime.jsx("dt", { className: nameStyle$1, children: humanizeField(field) }),
638
- /* @__PURE__ */ jsxRuntime.jsx("dd", { className: valueStyle, children: renderValue(value) })
679
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: nameStyle$1, children: verbatim ? field : humanizeField(field) }),
680
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: valueStyle, children: renderValue(value, verbatim) })
639
681
  ] }, field)) });
640
682
  }
641
683
  const listStyle$1 = css.css({
@@ -756,7 +798,11 @@ function BindingsSection({ bindings, title = "Bindings" }) {
756
798
  );
757
799
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: protocolCardStyle, children: [
758
800
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: protocolHeaderStyle, children: /* @__PURE__ */ jsxRuntime.jsx(ProtocolBadge, { protocol }) }),
759
- rows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(FieldRows, { fields: rows }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: emptyStyle, children: "No binding fields." })
801
+ rows.length > 0 ? (
802
+ // Binding/config keys are shown verbatim — their acronyms (URL,
803
+ // ID) and dotted names (cleanup.policy) must not be title-cased.
804
+ /* @__PURE__ */ jsxRuntime.jsx(FieldRows, { fields: rows, verbatim: true })
805
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: emptyStyle, children: "No binding fields." })
760
806
  ] }, protocol);
761
807
  }) })
762
808
  ] });
@@ -794,17 +840,20 @@ function SecurityRequirementBadges({ requirements, label = "Security" }) {
794
840
  if (!requirements || requirements.length === 0) return null;
795
841
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rowStyle, children: [
796
842
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: labelStyle, children: label }),
797
- requirements.map((alternative, altIndex) => /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
798
- altIndex > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: orStyle, children: "OR" }),
799
- alternative.map((scheme) => /* @__PURE__ */ jsxRuntime.jsxs("a", { href: `#security-${scheme.name}`, className: badgeStyle, children: [
843
+ requirements.map((alternative, altIndex) => {
844
+ const chips = alternative.map((scheme) => /* @__PURE__ */ jsxRuntime.jsxs("a", { href: `#security-${scheme.name}`, className: badgeStyle, children: [
800
845
  /* @__PURE__ */ jsxRuntime.jsx(useSpecFetcher.Icon, { name: "lock", size: "0.625rem", strokeWidth: 2.5 }),
801
846
  scheme.name,
802
847
  scheme.scopes.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: scopesStyle$1, children: [
803
848
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: scopeSepStyle, children: "\xB7" }),
804
849
  scheme.scopes.join(", ")
805
850
  ] })
806
- ] }, scheme.name))
807
- ] }, altIndex))
851
+ ] }, scheme.name));
852
+ return altIndex === 0 ? /* @__PURE__ */ jsxRuntime.jsx(React.Fragment, { children: chips }, altIndex) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: orGroupStyle, children: [
853
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: orStyle, children: "OR" }),
854
+ chips
855
+ ] }, altIndex);
856
+ })
808
857
  ] });
809
858
  }
810
859
  const rowStyle = css.css({
@@ -812,7 +861,16 @@ const rowStyle = css.css({
812
861
  alignItems: "center",
813
862
  gap: "0.375rem",
814
863
  flexWrap: "wrap",
815
- marginTop: "0.5rem"
864
+ marginTop: "0.5rem",
865
+ // Breathing room below the security row so it doesn't butt against the next
866
+ // section on narrow screens (finding 3).
867
+ marginBottom: "0.5rem"
868
+ });
869
+ const orGroupStyle = css.css({
870
+ display: "inline-flex",
871
+ alignItems: "center",
872
+ gap: "0.375rem",
873
+ whiteSpace: "nowrap"
816
874
  });
817
875
  const labelStyle = css.css({
818
876
  fontSize: "var(--omnispec-font-size-xxs)",
@@ -1010,6 +1068,56 @@ function ServerList({ servers }) {
1010
1068
  ] });
1011
1069
  }
1012
1070
 
1071
+ function TagChips({ tags, onTagClick }) {
1072
+ if (!tags || tags.length === 0) return null;
1073
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: tagChipsStyle$1, children: tags.map((tag) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: tagItemStyle, children: [
1074
+ onTagClick ? /* @__PURE__ */ jsxRuntime.jsx(
1075
+ "button",
1076
+ {
1077
+ type: "button",
1078
+ className: tagChipButtonStyle$1,
1079
+ title: tag.description,
1080
+ onClick: () => onTagClick(tag.name),
1081
+ children: tag.name
1082
+ }
1083
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: tagChipStyle$1, title: tag.description, children: tag.name }),
1084
+ tag.externalDocs && /* @__PURE__ */ jsxRuntime.jsx(ExternalDocsLink, { externalDocs: tag.externalDocs, context: tag.name })
1085
+ ] }, tag.name)) });
1086
+ }
1087
+ const tagChipsStyle$1 = css.css({
1088
+ display: "flex",
1089
+ flexWrap: "wrap",
1090
+ alignItems: "center",
1091
+ gap: "0.5rem",
1092
+ marginBottom: "0.5rem"
1093
+ });
1094
+ const tagItemStyle = css.css({
1095
+ display: "inline-flex",
1096
+ alignItems: "center",
1097
+ gap: "0.25rem"
1098
+ });
1099
+ const tagChipStyle$1 = css.css({
1100
+ fontSize: "var(--omnispec-font-size-xxs)",
1101
+ fontFamily: "var(--omnispec-font-mono)",
1102
+ color: "var(--omnispec-fg-secondary)",
1103
+ backgroundColor: "var(--omnispec-bg-tertiary)",
1104
+ padding: "1px 0.5rem",
1105
+ borderRadius: "0.625rem"
1106
+ });
1107
+ const tagChipButtonStyle$1 = css.css({
1108
+ fontSize: "var(--omnispec-font-size-xxs)",
1109
+ fontFamily: "var(--omnispec-font-mono)",
1110
+ color: "var(--omnispec-fg-secondary)",
1111
+ backgroundColor: "var(--omnispec-bg-tertiary)",
1112
+ padding: "1px 0.5rem",
1113
+ borderRadius: "0.625rem",
1114
+ border: "none",
1115
+ cursor: "pointer",
1116
+ "&:hover": {
1117
+ color: "var(--omnispec-fg-primary)"
1118
+ }
1119
+ });
1120
+
1013
1121
  function detectSchemaFormat(schemaFormat) {
1014
1122
  if (!schemaFormat) return "json";
1015
1123
  const f = schemaFormat.toLowerCase();
@@ -1088,7 +1196,11 @@ function primitive(name) {
1088
1196
  function MessagePayload({ message, idx }) {
1089
1197
  const rawPayload = message.payload;
1090
1198
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1091
- message.tags && message.tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: tagChipsStyle$2, children: message.tags.map((tag) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: tagChipStyle$2, title: tag.description, children: tag.name }, tag.name)) }),
1199
+ message.tags && message.tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TagChips, { tags: message.tags }),
1200
+ message.appliedTraits && message.appliedTraits.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: traitsRowStyle, children: [
1201
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: traitsLabelStyle, children: "Traits:" }),
1202
+ message.appliedTraits.map((trait, i) => /* @__PURE__ */ jsxRuntime.jsx("code", { className: traitChipStyle, children: trait }, `${trait}-${i}`))
1203
+ ] }),
1092
1204
  message.externalDocs && /* @__PURE__ */ jsxRuntime.jsx("div", { className: externalDocsStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
1093
1205
  ExternalDocsLink,
1094
1206
  {
@@ -1117,10 +1229,23 @@ function PayloadBody({
1117
1229
  }
1118
1230
  const format = detectSchemaFormat(schemaFormat);
1119
1231
  if (format === "protobuf") {
1120
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1121
- /* @__PURE__ */ jsxRuntime.jsx(Collapsible.CodeBlock, { code: JSON.stringify(rawPayload, null, 2), language: "json" }),
1122
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: proNoteStyle, children: "Full Protobuf schema rendering is available in Pro." })
1123
- ] });
1232
+ const isSource = typeof rawPayload === "string";
1233
+ const code = isSource ? rawPayload : JSON.stringify(rawPayload, null, 2);
1234
+ return /* @__PURE__ */ jsxRuntime.jsx(
1235
+ Collapsible.Tabs,
1236
+ {
1237
+ tabs: [
1238
+ {
1239
+ id: `schema-${idx}`,
1240
+ label: "Payload Schema",
1241
+ content: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1242
+ /* @__PURE__ */ jsxRuntime.jsx(Collapsible.CodeBlock, { code, language: isSource ? "text" : "json" }),
1243
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: proNoteStyle, children: "Full Protobuf schema rendering is available in Pro." })
1244
+ ] })
1245
+ }
1246
+ ]
1247
+ }
1248
+ );
1124
1249
  }
1125
1250
  const schema = format === "avro" ? avroToJsonSchema(rawPayload) : rawPayload;
1126
1251
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -1156,22 +1281,28 @@ function MessageExample({ message, schema }) {
1156
1281
  /* @__PURE__ */ jsxRuntime.jsx(Collapsible.CodeBlock, { code, language: "json" })
1157
1282
  ] });
1158
1283
  }
1159
- const tagChipsStyle$2 = css.css({
1284
+ const externalDocsStyle = css.css({
1285
+ marginBottom: "0.5rem"
1286
+ });
1287
+ const traitsRowStyle = css.css({
1160
1288
  display: "flex",
1289
+ alignItems: "center",
1161
1290
  flexWrap: "wrap",
1162
- gap: "0.25rem",
1291
+ gap: "0.375rem",
1163
1292
  marginBottom: "0.5rem"
1164
1293
  });
1165
- const tagChipStyle$2 = css.css({
1166
- fontSize: "var(--omnispec-font-size-xxs)",
1294
+ const traitsLabelStyle = css.css({
1295
+ fontSize: "var(--omnispec-font-size-xs)",
1296
+ fontWeight: 700,
1297
+ color: "var(--omnispec-fg-muted)"
1298
+ });
1299
+ const traitChipStyle = css.css({
1167
1300
  fontFamily: "var(--omnispec-font-mono)",
1301
+ fontSize: "var(--omnispec-font-size-xxs)",
1168
1302
  color: "var(--omnispec-fg-secondary)",
1169
1303
  backgroundColor: "var(--omnispec-bg-tertiary)",
1170
- padding: "1px 0.5rem",
1171
- borderRadius: "0.625rem"
1172
- });
1173
- const externalDocsStyle = css.css({
1174
- marginBottom: "0.5rem"
1304
+ padding: "0.0625rem 0.375rem",
1305
+ borderRadius: "var(--omnispec-border-radius)"
1175
1306
  });
1176
1307
  const schemaFormatBadgeStyle = css.css({
1177
1308
  display: "inline-block",
@@ -1232,6 +1363,7 @@ function ChannelDetail({ channel, id, expandAll, expandGeneration, onTagClick })
1232
1363
  /* @__PURE__ */ jsxRuntime.jsx(Collapsible.MethodBar, { label: primaryLabel, path: channel.address, color: primaryColor }),
1233
1364
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: bodyStyle, children: [
1234
1365
  channel.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: sectionStyle$1, children: /* @__PURE__ */ jsxRuntime.jsx(Collapsible.MarkdownRenderer, { content: channel.description }) }),
1366
+ channel.tags && channel.tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TagChips, { tags: channel.tags, onTagClick }),
1235
1367
  channel.externalDocs && /* @__PURE__ */ jsxRuntime.jsx("div", { className: sectionStyle$1, children: /* @__PURE__ */ jsxRuntime.jsx(ExternalDocsLink, { externalDocs: channel.externalDocs, context: channel.address }) }),
1236
1368
  channel.parameters && Object.keys(channel.parameters).length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: sectionStyle$1, children: [
1237
1369
  /* @__PURE__ */ jsxRuntime.jsx("h4", { className: sectionTitleStyle$1, children: "Parameters" }),
@@ -1261,32 +1393,22 @@ function ChannelDetail({ channel, id, expandAll, expandGeneration, onTagClick })
1261
1393
  /* @__PURE__ */ jsxRuntime.jsx(BindingsSection, { bindings: channel.bindings, title: "Channel Bindings" }),
1262
1394
  channel.operations.map((op, idx) => /* @__PURE__ */ jsxRuntime.jsxs("div", { id: id ? `${id}-op-${idx}` : void 0, className: operationStyle, children: [
1263
1395
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: opHeaderStyle$1, children: [
1264
- /* @__PURE__ */ jsxRuntime.jsx(
1265
- "span",
1266
- {
1267
- className: css.cx(actionBadgeBase$1, css.css({
1268
- backgroundColor: actionColors$1[op.action] ?? "var(--omnispec-fg-muted)"
1269
- })),
1270
- children: actionLabels$1[op.action] ?? op.action.toUpperCase()
1271
- }
1272
- ),
1273
- op.operationId && /* @__PURE__ */ jsxRuntime.jsx("span", { className: opIdStyle$1, children: op.operationId }),
1274
- op.title && /* @__PURE__ */ jsxRuntime.jsx("span", { className: opTitleStyle$1, children: op.title }),
1396
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: opNameLineStyle, children: [
1397
+ /* @__PURE__ */ jsxRuntime.jsx(
1398
+ "span",
1399
+ {
1400
+ className: css.cx(actionBadgeBase$1, css.css({
1401
+ backgroundColor: actionColors$1[op.action] ?? "var(--omnispec-fg-muted)"
1402
+ })),
1403
+ children: actionLabels$1[op.action] ?? op.action.toUpperCase()
1404
+ }
1405
+ ),
1406
+ op.operationId && /* @__PURE__ */ jsxRuntime.jsx("span", { className: opIdStyle$1, children: op.operationId }),
1407
+ op.title && /* @__PURE__ */ jsxRuntime.jsx("span", { className: opTitleStyle$1, children: op.title })
1408
+ ] }),
1275
1409
  op.summary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: opSummaryStyle$1, children: op.summary })
1276
1410
  ] }),
1277
- op.tags && op.tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: tagChipsStyle$1, children: op.tags.map(
1278
- (tag) => onTagClick ? /* @__PURE__ */ jsxRuntime.jsx(
1279
- "button",
1280
- {
1281
- type: "button",
1282
- className: tagChipButtonStyle$1,
1283
- title: tag.description,
1284
- onClick: () => onTagClick(tag.name),
1285
- children: tag.name
1286
- },
1287
- tag.name
1288
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: tagChipStyle$1, title: tag.description, children: tag.name }, tag.name)
1289
- ) }),
1411
+ op.tags && op.tags.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TagChips, { tags: op.tags, onTagClick }),
1290
1412
  op.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: sectionStyle$1, children: /* @__PURE__ */ jsxRuntime.jsx(Collapsible.MarkdownRenderer, { content: op.description }) }),
1291
1413
  op.externalDocs && /* @__PURE__ */ jsxRuntime.jsx("div", { className: opExternalDocsStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
1292
1414
  ExternalDocsLink,
@@ -1303,8 +1425,9 @@ function ChannelDetail({ channel, id, expandAll, expandGeneration, onTagClick })
1303
1425
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: replyMetaLabelStyle, children: "channel" }),
1304
1426
  /* @__PURE__ */ jsxRuntime.jsx("code", { className: replyMetaValueStyle, children: op.reply.channelName })
1305
1427
  ] }),
1306
- op.reply.address && (op.reply.address.location || op.reply.address.description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: replyMetaRowStyle, children: [
1428
+ op.reply.address && (op.reply.address.value || op.reply.address.location || op.reply.address.description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: replyMetaRowStyle, children: [
1307
1429
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: replyMetaLabelStyle, children: "address" }),
1430
+ op.reply.address.value && /* @__PURE__ */ jsxRuntime.jsx("code", { className: replyMetaValueStyle, children: op.reply.address.value }),
1308
1431
  op.reply.address.location && /* @__PURE__ */ jsxRuntime.jsx("code", { className: replyMetaValueStyle, children: op.reply.address.location }),
1309
1432
  op.reply.address.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: replyMetaDescStyle, children: op.reply.address.description })
1310
1433
  ] }),
@@ -1358,10 +1481,10 @@ function MessageView({ message, idx }) {
1358
1481
  message.summary && /* @__PURE__ */ jsxRuntime.jsx("p", { className: messageSummaryStyle$1, children: message.summary }),
1359
1482
  message.contentType && /* @__PURE__ */ jsxRuntime.jsx("code", { className: contentTypeStyle$2, children: message.contentType }),
1360
1483
  /* @__PURE__ */ jsxRuntime.jsx(MessagePayload, { message, idx }),
1361
- message.correlationId && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: correlationIdStyle, children: [
1362
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: correlationIdLabelStyle, children: "correlationId" }),
1363
- /* @__PURE__ */ jsxRuntime.jsx("code", { className: correlationIdLocationStyle, children: message.correlationId.location }),
1364
- message.correlationId.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: correlationIdDescStyle, children: message.correlationId.description })
1484
+ message.correlationId && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: correlationIdStyle$1, children: [
1485
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: correlationIdLabelStyle$1, children: "correlationId" }),
1486
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: correlationIdLocationStyle$1, children: message.correlationId.location }),
1487
+ message.correlationId.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: correlationIdDescStyle$1, children: message.correlationId.description })
1365
1488
  ] }),
1366
1489
  message.headers && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: headersWrapStyle$1, children: [
1367
1490
  /* @__PURE__ */ jsxRuntime.jsx("h4", { className: sectionTitleStyle$1, children: "Headers" }),
@@ -1428,9 +1551,16 @@ const operationStyle = css.css({
1428
1551
  const opHeaderStyle$1 = css.css({
1429
1552
  display: "flex",
1430
1553
  alignItems: "center",
1431
- gap: "0.5rem",
1554
+ flexWrap: "wrap",
1555
+ gap: "0.25rem 0.5rem",
1432
1556
  marginBottom: "0.5rem"
1433
1557
  });
1558
+ const opNameLineStyle = css.css({
1559
+ display: "inline-flex",
1560
+ alignItems: "center",
1561
+ flexWrap: "wrap",
1562
+ gap: "0.5rem"
1563
+ });
1434
1564
  const actionBadgeBase$1 = css.css({
1435
1565
  display: "inline-block",
1436
1566
  padding: "0.125rem 0.375rem",
@@ -1453,6 +1583,7 @@ const opTitleStyle$1 = css.css({
1453
1583
  color: "var(--omnispec-fg-primary)"
1454
1584
  });
1455
1585
  const opSummaryStyle$1 = css.css({
1586
+ flexBasis: "100%",
1456
1587
  fontSize: "var(--omnispec-font-size-sm)",
1457
1588
  color: "var(--omnispec-fg-secondary)"
1458
1589
  });
@@ -1508,20 +1639,20 @@ const messageCountStyle = css.css({
1508
1639
  fontSize: "var(--omnispec-font-size-xs)",
1509
1640
  color: "var(--omnispec-fg-muted)"
1510
1641
  });
1511
- const correlationIdStyle = css.css({
1642
+ const correlationIdStyle$1 = css.css({
1512
1643
  display: "flex",
1513
1644
  alignItems: "center",
1514
1645
  gap: "0.5rem",
1515
1646
  flexWrap: "wrap",
1516
1647
  marginTop: "0.75rem"
1517
1648
  });
1518
- const correlationIdLabelStyle = css.css({
1649
+ const correlationIdLabelStyle$1 = css.css({
1519
1650
  fontSize: "var(--omnispec-font-size-xs)",
1520
1651
  fontWeight: 700,
1521
1652
  color: "var(--omnispec-fg-primary)",
1522
1653
  fontFamily: "var(--omnispec-font-mono)"
1523
1654
  });
1524
- const correlationIdLocationStyle = css.css({
1655
+ const correlationIdLocationStyle$1 = css.css({
1525
1656
  fontFamily: "var(--omnispec-font-mono)",
1526
1657
  fontSize: "var(--omnispec-font-size-xs)",
1527
1658
  color: "var(--omnispec-fg-code)",
@@ -1529,7 +1660,7 @@ const correlationIdLocationStyle = css.css({
1529
1660
  padding: "1px 0.375rem",
1530
1661
  borderRadius: "0.1875rem"
1531
1662
  });
1532
- const correlationIdDescStyle = css.css({
1663
+ const correlationIdDescStyle$1 = css.css({
1533
1664
  fontSize: "var(--omnispec-font-size-xs)",
1534
1665
  color: "var(--omnispec-fg-secondary)"
1535
1666
  });
@@ -1570,33 +1701,6 @@ const replyMetaDescStyle = css.css({
1570
1701
  fontSize: "var(--omnispec-font-size-xs)",
1571
1702
  color: "var(--omnispec-fg-secondary)"
1572
1703
  });
1573
- const tagChipsStyle$1 = css.css({
1574
- display: "flex",
1575
- flexWrap: "wrap",
1576
- gap: "0.25rem",
1577
- marginBottom: "0.5rem"
1578
- });
1579
- const tagChipStyle$1 = css.css({
1580
- fontSize: "var(--omnispec-font-size-xxs)",
1581
- fontFamily: "var(--omnispec-font-mono)",
1582
- color: "var(--omnispec-fg-secondary)",
1583
- backgroundColor: "var(--omnispec-bg-tertiary)",
1584
- padding: "1px 0.5rem",
1585
- borderRadius: "0.625rem"
1586
- });
1587
- const tagChipButtonStyle$1 = css.css({
1588
- fontSize: "var(--omnispec-font-size-xxs)",
1589
- fontFamily: "var(--omnispec-font-mono)",
1590
- color: "var(--omnispec-fg-secondary)",
1591
- backgroundColor: "var(--omnispec-bg-tertiary)",
1592
- padding: "1px 0.5rem",
1593
- borderRadius: "0.625rem",
1594
- border: "none",
1595
- cursor: "pointer",
1596
- "&:hover": {
1597
- color: "var(--omnispec-fg-primary)"
1598
- }
1599
- });
1600
1704
 
1601
1705
  const actionLabels = {
1602
1706
  publish: "PUB",
@@ -1902,11 +2006,13 @@ function ComponentsSection({ components }) {
1902
2006
  children: [
1903
2007
  message.title && /* @__PURE__ */ jsxRuntime.jsx("p", { className: messageSummaryStyle, children: message.title }),
1904
2008
  message.summary && /* @__PURE__ */ jsxRuntime.jsx("p", { className: messageSummaryStyle, children: message.summary }),
1905
- message.contentType && /* @__PURE__ */ jsxRuntime.jsxs("code", { className: contentTypeStyle, children: [
1906
- message.contentType,
1907
- message.contentTypeInherited && /* @__PURE__ */ jsxRuntime.jsx("span", { className: contentTypeHintStyle, children: " (default)" })
1908
- ] }),
2009
+ message.contentType && /* @__PURE__ */ jsxRuntime.jsx("code", { className: contentTypeStyle, children: message.contentType }),
1909
2010
  /* @__PURE__ */ jsxRuntime.jsx(MessagePayload, { message, idx: `message-${name}` }),
2011
+ message.correlationId && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: correlationIdStyle, children: [
2012
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: correlationIdLabelStyle, children: "correlationId" }),
2013
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: correlationIdLocationStyle, children: message.correlationId.location }),
2014
+ message.correlationId.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: correlationIdDescStyle, children: message.correlationId.description })
2015
+ ] }),
1910
2016
  message.headers && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: headersWrapStyle, children: [
1911
2017
  /* @__PURE__ */ jsxRuntime.jsx("h4", { className: headersTitleStyle, children: "Headers" }),
1912
2018
  /* @__PURE__ */ jsxRuntime.jsx(Collapsible.SchemaTree, { nodes: Collapsible.schemaToNodes(message.headers) })
@@ -1975,9 +2081,30 @@ const contentTypeStyle = css.css({
1975
2081
  color: "var(--omnispec-fg-muted)",
1976
2082
  marginBottom: "0.5rem"
1977
2083
  });
1978
- const contentTypeHintStyle = css.css({
1979
- fontStyle: "italic",
1980
- color: "var(--omnispec-fg-muted)"
2084
+ const correlationIdStyle = css.css({
2085
+ display: "flex",
2086
+ alignItems: "center",
2087
+ gap: "0.5rem",
2088
+ flexWrap: "wrap",
2089
+ marginTop: "0.75rem"
2090
+ });
2091
+ const correlationIdLabelStyle = css.css({
2092
+ fontSize: "var(--omnispec-font-size-xs)",
2093
+ fontWeight: 700,
2094
+ color: "var(--omnispec-fg-primary)",
2095
+ fontFamily: "var(--omnispec-font-mono)"
2096
+ });
2097
+ const correlationIdLocationStyle = css.css({
2098
+ fontFamily: "var(--omnispec-font-mono)",
2099
+ fontSize: "var(--omnispec-font-size-xs)",
2100
+ color: "var(--omnispec-fg-code)",
2101
+ backgroundColor: "var(--omnispec-bg-tertiary)",
2102
+ padding: "1px 0.375rem",
2103
+ borderRadius: "0.1875rem"
2104
+ });
2105
+ const correlationIdDescStyle = css.css({
2106
+ fontSize: "var(--omnispec-font-size-xs)",
2107
+ color: "var(--omnispec-fg-secondary)"
1981
2108
  });
1982
2109
  const headersWrapStyle = css.css({
1983
2110
  marginTop: "1rem"
@@ -2150,7 +2277,7 @@ function channelTagNames(channel) {
2150
2277
  function hasAnyTags(channels) {
2151
2278
  return channels.some((ch) => channelTagNames(ch).length > 0);
2152
2279
  }
2153
- function groupChannelsByTag(channels, specTags) {
2280
+ function groupChannelsByTag(channels, specTags, xTagGroups) {
2154
2281
  const byName = /* @__PURE__ */ new Map();
2155
2282
  const untagged = [];
2156
2283
  for (const channel of channels) {
@@ -2165,9 +2292,13 @@ function groupChannelsByTag(channels, specTags) {
2165
2292
  byName.set(name, list);
2166
2293
  }
2167
2294
  }
2295
+ const xGrouped = (xTagGroups ?? []).flatMap((xg) => xg.tags).filter((name) => byName.has(name));
2168
2296
  const declared = (specTags ?? []).map((t) => t.name);
2169
- const operationOnly = Array.from(byName.keys()).filter((name) => !declared.includes(name)).sort();
2170
- const orderedNames = [...declared.filter((name) => byName.has(name)), ...operationOnly];
2297
+ const seen = new Set(xGrouped);
2298
+ const declaredRemaining = declared.filter((name) => byName.has(name) && !seen.has(name));
2299
+ declaredRemaining.forEach((name) => seen.add(name));
2300
+ const operationOnly = Array.from(byName.keys()).filter((name) => !seen.has(name)).sort();
2301
+ const orderedNames = [.../* @__PURE__ */ new Set([...xGrouped, ...declaredRemaining, ...operationOnly])];
2171
2302
  const tagMetaByName = new Map((specTags ?? []).map((t) => [t.name, t]));
2172
2303
  const usedIds = /* @__PURE__ */ new Set();
2173
2304
  const uniqueId = (base) => {
@@ -2254,10 +2385,26 @@ function AsyncApiSpec({
2254
2385
  const [allExpanded, setAllExpanded] = React.useState(defaultExpandOperations);
2255
2386
  const [expandGeneration, setExpandGeneration] = React.useState(0);
2256
2387
  const [navGrouping, setNavGrouping] = React.useState("channel");
2388
+ const [activeTagFilter, setActiveTagFilter] = React.useState(null);
2257
2389
  const handleToggleExpand = React.useCallback(() => {
2258
2390
  setAllExpanded((prev) => !prev);
2259
2391
  setExpandGeneration((prev) => prev + 1);
2260
2392
  }, []);
2393
+ const handleTagFilter = React.useCallback((tagName) => {
2394
+ setActiveTagFilter((prev) => {
2395
+ const next = prev === tagName ? null : tagName;
2396
+ setSearchQuery(next ?? "");
2397
+ return next;
2398
+ });
2399
+ }, []);
2400
+ const handleSearch = React.useCallback((query) => {
2401
+ setSearchQuery(query);
2402
+ setActiveTagFilter(null);
2403
+ }, []);
2404
+ const clearTagFilter = React.useCallback(() => {
2405
+ setActiveTagFilter(null);
2406
+ setSearchQuery("");
2407
+ }, []);
2261
2408
  React.useMemo(() => {
2262
2409
  if (parsedSpec && onSpecLoaded) {
2263
2410
  const major = parseInt(parsedSpec.specVersion.split(".")[0], 10);
@@ -2279,7 +2426,7 @@ function AsyncApiSpec({
2279
2426
  }, [parsedSpec, searchQuery]);
2280
2427
  const grouped = React.useMemo(() => hasAnyTags(filteredChannels), [filteredChannels]);
2281
2428
  const tagGroups = React.useMemo(
2282
- () => grouped ? groupChannelsByTag(filteredChannels, parsedSpec?.tags) : [],
2429
+ () => grouped ? groupChannelsByTag(filteredChannels, parsedSpec?.tags, parsedSpec?.tagGroups) : [],
2283
2430
  [grouped, filteredChannels, parsedSpec]
2284
2431
  );
2285
2432
  const channelDomId = React.useMemo(() => {
@@ -2311,6 +2458,10 @@ function AsyncApiSpec({
2311
2458
  () => filteredChannels.some((ch) => ch.operations.length > 0),
2312
2459
  [filteredChannels]
2313
2460
  );
2461
+ const unassignedOpCount = React.useMemo(
2462
+ () => parsedSpec?.channels.filter((ch) => ch.name === "__unassigned__").reduce((sum, ch) => sum + ch.operations.length, 0) ?? 0,
2463
+ [parsedSpec]
2464
+ );
2314
2465
  const navItems = React.useMemo(() => {
2315
2466
  if (!parsedSpec) return [];
2316
2467
  const actionLabel = (action) => action.toUpperCase();
@@ -2413,9 +2564,27 @@ function AsyncApiSpec({
2413
2564
  Collapsible.SearchBar,
2414
2565
  {
2415
2566
  placeholder: "Filter channels...",
2416
- onSearch: setSearchQuery
2567
+ onSearch: handleSearch,
2568
+ value: searchQuery
2417
2569
  }
2418
2570
  ),
2571
+ activeTagFilter && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: activeFilterStyle, children: [
2572
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: activeFilterLabelStyle, children: "Filtered by tag:" }),
2573
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: activeFilterTagStyle, children: activeTagFilter }),
2574
+ /* @__PURE__ */ jsxRuntime.jsxs(
2575
+ "button",
2576
+ {
2577
+ type: "button",
2578
+ className: activeFilterClearStyle,
2579
+ onClick: clearTagFilter,
2580
+ "aria-label": `Clear tag filter: ${activeTagFilter}`,
2581
+ children: [
2582
+ "Clear",
2583
+ /* @__PURE__ */ jsxRuntime.jsx(useSpecFetcher.Icon, { name: "xmark", size: "0.75rem", strokeWidth: 2.5 })
2584
+ ]
2585
+ }
2586
+ )
2587
+ ] }),
2419
2588
  hasOperations && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: groupToggleStyle, role: "group", "aria-label": "Sidebar grouping", children: [
2420
2589
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: groupToggleLabelStyle, children: "Group by" }),
2421
2590
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: groupToggleButtonsStyle, children: [
@@ -2478,6 +2647,7 @@ function AsyncApiSpec({
2478
2647
  /* @__PURE__ */ jsxRuntime.jsx("div", { id: "servers", children: /* @__PURE__ */ jsxRuntime.jsx(ServerList, { servers: parsedSpec.servers }) }),
2479
2648
  /* @__PURE__ */ jsxRuntime.jsxs("div", { id: "channels", children: [
2480
2649
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: css.css({ margin: "0 0 1rem", fontSize: "var(--omnispec-h2-font-size)", fontWeight: "var(--omnispec-h2-font-weight)", color: "var(--omnispec-h2-color)" }), children: "Channels" }),
2650
+ unassignedOpCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: unassignedNoticeStyle, role: "alert", children: `Unresolved reference: ${unassignedOpCount} ${unassignedOpCount === 1 ? "operation references" : "operations reference"} an undefined channel. ${unassignedOpCount === 1 ? "It is" : "They are"} listed under "Unassigned operations" below rather than dropped.` }),
2481
2651
  filteredChannels.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: emptyStateStyle, children: parsedSpec.channels.length === 0 ? "No channels are defined for this API." : "No channels match your filter." }) : grouped ? tagGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("section", { id: `taggroup-${group.id}`, className: tagSectionStyle, children: [
2482
2652
  /* @__PURE__ */ jsxRuntime.jsx(TagGroupHeader, { group }),
2483
2653
  group.channels.map((channel) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -2488,7 +2658,7 @@ function AsyncApiSpec({
2488
2658
  displayMode: resolvedDisplayMode,
2489
2659
  expandAll: allExpanded,
2490
2660
  expandGeneration,
2491
- onTagClick: setSearchQuery
2661
+ onTagClick: handleTagFilter
2492
2662
  },
2493
2663
  `${group.id}-${channel.name}`
2494
2664
  ))
@@ -2500,7 +2670,7 @@ function AsyncApiSpec({
2500
2670
  displayMode: resolvedDisplayMode,
2501
2671
  expandAll: allExpanded,
2502
2672
  expandGeneration,
2503
- onTagClick: setSearchQuery
2673
+ onTagClick: handleTagFilter
2504
2674
  },
2505
2675
  channel.name
2506
2676
  ))
@@ -2553,6 +2723,17 @@ const emptyStateStyle = css.css({
2553
2723
  color: "var(--omnispec-fg-muted)",
2554
2724
  fontStyle: "italic"
2555
2725
  });
2726
+ const unassignedNoticeStyle = css.css({
2727
+ display: "flex",
2728
+ alignItems: "center",
2729
+ gap: "0.5rem",
2730
+ padding: "0.625rem 0.875rem",
2731
+ marginBottom: "1rem",
2732
+ borderRadius: "var(--omnispec-border-radius)",
2733
+ backgroundColor: "color-mix(in srgb, var(--omnispec-color-warning) 12%, transparent)",
2734
+ color: "var(--omnispec-color-warning)",
2735
+ fontSize: "var(--omnispec-font-size-sm)"
2736
+ });
2556
2737
  const noResultsNavStyle = css.css({
2557
2738
  fontStyle: "italic",
2558
2739
  color: "var(--omnispec-fg-muted)",
@@ -2596,6 +2777,48 @@ const groupToggleActiveStyle = css.css({
2596
2777
  const tagSectionStyle = css.css({
2597
2778
  marginBottom: "2rem"
2598
2779
  });
2780
+ const activeFilterStyle = css.css({
2781
+ display: "flex",
2782
+ alignItems: "center",
2783
+ flexWrap: "wrap",
2784
+ gap: "0.375rem",
2785
+ padding: "0 0.75rem 0.5rem"
2786
+ });
2787
+ const activeFilterLabelStyle = css.css({
2788
+ fontSize: "var(--omnispec-font-size-xxs)",
2789
+ fontWeight: 600,
2790
+ textTransform: "uppercase",
2791
+ letterSpacing: "0.05em",
2792
+ color: "var(--omnispec-fg-muted)"
2793
+ });
2794
+ const activeFilterTagStyle = css.css({
2795
+ fontFamily: "var(--omnispec-font-mono)",
2796
+ fontSize: "var(--omnispec-font-size-xs)",
2797
+ fontWeight: 500,
2798
+ color: "var(--omnispec-fg-primary)",
2799
+ backgroundColor: "var(--omnispec-bg-tertiary)",
2800
+ padding: "0.125rem 0.5rem",
2801
+ borderRadius: "var(--omnispec-border-radius)"
2802
+ });
2803
+ const activeFilterClearStyle = css.css({
2804
+ display: "inline-flex",
2805
+ alignItems: "center",
2806
+ gap: "0.1875rem",
2807
+ border: "none",
2808
+ background: "transparent",
2809
+ cursor: "pointer",
2810
+ marginLeft: "auto",
2811
+ padding: "0.125rem 0.25rem",
2812
+ borderRadius: "var(--omnispec-border-radius)",
2813
+ fontSize: "var(--omnispec-font-size-xxs)",
2814
+ fontWeight: 600,
2815
+ textTransform: "uppercase",
2816
+ letterSpacing: "0.05em",
2817
+ color: "var(--omnispec-color-primary)",
2818
+ "&:hover": {
2819
+ backgroundColor: "var(--omnispec-bg-tertiary)"
2820
+ }
2821
+ });
2599
2822
 
2600
2823
  exports.AsyncApiSpec = AsyncApiSpec;
2601
- //# sourceMappingURL=AsyncApiSpec-BzgUioEK.js.map
2824
+ //# sourceMappingURL=AsyncApiSpec-B-RGmOUL.js.map