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

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
@@ -2,7 +2,7 @@ import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import { useCallback, Fragment, useState, useMemo } from 'react';
3
3
  import { cx, css } from '@emotion/css';
4
4
  import { a as useSpecFetcher, I as Icon, m as mq, r as resolveSchemaStyle, S as SpecType, T as ThemeProvider, L as LoadingScreen, b as SpecStatusScreen, E as ErrorMessage, N as NavTree, h as buildSidebar, C as ConfigProvider, e as DocLayout } from './useSpecFetcher-BvVfMuzf.js';
5
- import { M as MarkdownRenderer, C as CodeBlock, T as Tabs, t as SchemaTree, A as schemaToNodes, x as generateExample, J as ExampleSelector, d as ExpandableCard, f as MethodBar, K as buildConstraints, S as SchemaBadge, R as ReferenceLayout, a as Collapsible, D as useHashScroll, y as requestExpand, w as flattenNavItemIds, G as useScrollSpy, u as SearchBar, L as ExpandProvider, v as SpecToolbar, z as resolveDownloadLink } from './Collapsible-MyJB2agz.js';
5
+ import { M as MarkdownRenderer, T as Tabs, C as CodeBlock, t as SchemaTree, A as schemaToNodes, x as generateExample, J as ExampleSelector, d as ExpandableCard, f as MethodBar, K as buildConstraints, S as SchemaBadge, R as ReferenceLayout, a as Collapsible, D as useHashScroll, y as requestExpand, w as flattenNavItemIds, G as useScrollSpy, u as SearchBar, L as ExpandProvider, v as SpecToolbar, z as resolveDownloadLink } from './Collapsible-CPxS90KX.js';
6
6
  import { E as ErrorBoundary } from './ErrorBoundary-9pDlZFfM.js';
7
7
  import { r as resolveRefs, p as parse, d as decodeJsonPointerSegment } from './ref-resolver-B0C36NuQ.js';
8
8
  import '@emotion/styled';
@@ -39,6 +39,16 @@ function mergeMessageLayer(base, override) {
39
39
  }
40
40
  return result;
41
41
  }
42
+ function deriveAppliedTraitNames(traits, rawTraits) {
43
+ return traits.map((trait, i) => {
44
+ const raw = rawTraits?.[i];
45
+ if (raw && typeof raw === "object" && typeof raw.$ref === "string") {
46
+ const last = raw.$ref.split("/").pop();
47
+ if (last) return decodeJsonPointerSegment(last);
48
+ }
49
+ return trait.name ?? trait.title ?? `trait ${i + 1}`;
50
+ });
51
+ }
42
52
  function applyMessageTraits(msg, isV3) {
43
53
  if (!msg.traits || msg.traits.length === 0) return msg;
44
54
  if (isV3) {
@@ -82,7 +92,7 @@ async function parseAsyncApiSpec(specInput) {
82
92
  const rawApi = raw;
83
93
  const defaultContentType = api.defaultContentType;
84
94
  const channels = isV3 ? extractChannelsV3(api, rawApi, defaultContentType) : extractChannelsV2(api, defaultContentType);
85
- const components = extractComponents(api, isV3, defaultContentType);
95
+ const components = extractComponents(api, rawApi, isV3, defaultContentType);
86
96
  const root = api;
87
97
  const tags = api.tags?.map((tag) => {
88
98
  const raw2 = tag;
@@ -197,11 +207,20 @@ function resolveChannelName(rawChannel, resolvedChannel, addressToName) {
197
207
  }
198
208
  return void 0;
199
209
  }
210
+ function normalizeReplyAddress(address) {
211
+ if (typeof address === "string") {
212
+ return address.length > 0 ? { value: address } : void 0;
213
+ }
214
+ if (address && typeof address === "object") {
215
+ const { location, description } = address;
216
+ if (location || description) return { location, description };
217
+ }
218
+ return void 0;
219
+ }
200
220
  function extractChannelsV3(api, rawApi, defaultContentType) {
201
- if (!api.channels) return [];
202
221
  const channelMap = /* @__PURE__ */ new Map();
203
222
  const addressToName = /* @__PURE__ */ new Map();
204
- for (const [channelName, ch] of Object.entries(api.channels)) {
223
+ for (const [channelName, ch] of Object.entries(api.channels ?? {})) {
205
224
  const raw = ch;
206
225
  const address = ch.address ?? channelName;
207
226
  addressToName.set(address, channelName);
@@ -245,9 +264,10 @@ function extractChannelsV3(api, rawApi, defaultContentType) {
245
264
  addressToName
246
265
  );
247
266
  const raw = op;
248
- const reply = replyMessages.length > 0 || op.reply?.address || replyChannelName ? {
267
+ const replyAddress = normalizeReplyAddress(op.reply?.address);
268
+ const reply = replyMessages.length > 0 || replyAddress || replyChannelName ? {
249
269
  messages: replyMessages,
250
- address: op.reply?.address,
270
+ address: replyAddress,
251
271
  channelName: replyChannelName
252
272
  } : void 0;
253
273
  const operation = {
@@ -281,16 +301,17 @@ function extractChannelsV3(api, rawApi, defaultContentType) {
281
301
  channels.push({
282
302
  name: "__unassigned__",
283
303
  address: "Unassigned operations",
284
- description: "These operations reference a channel that is not defined in this document.",
304
+ 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.",
285
305
  operations: orphanOperations
286
306
  });
287
307
  }
288
308
  return channels;
289
309
  }
290
- function convertMessage(rawMsg, isV3, defaultContentType) {
310
+ function convertMessage(rawMsg, isV3, defaultContentType, rawTraits) {
291
311
  const msg = applyMessageTraits(rawMsg, isV3);
292
312
  const hasOwnContentType = typeof msg.contentType === "string" && msg.contentType.length > 0;
293
313
  const contentType = hasOwnContentType ? msg.contentType : defaultContentType ?? "application/json";
314
+ const appliedTraits = rawMsg.traits && rawMsg.traits.length > 0 ? deriveAppliedTraitNames(rawMsg.traits, rawTraits) : void 0;
294
315
  return {
295
316
  name: msg.name,
296
317
  title: msg.title,
@@ -305,15 +326,17 @@ function convertMessage(rawMsg, isV3, defaultContentType) {
305
326
  tags: msg.tags,
306
327
  examples: msg.examples,
307
328
  bindings: msg.bindings,
308
- externalDocs: msg.externalDocs
329
+ externalDocs: msg.externalDocs,
330
+ appliedTraits
309
331
  };
310
332
  }
311
- function extractComponents(api, isV3, defaultContentType) {
333
+ function extractComponents(api, rawApi, isV3, defaultContentType) {
312
334
  const components = api.components;
313
335
  const rawMessages = api.components?.messages ?? {};
336
+ const rawComponentMessages = rawApi.components?.messages ?? {};
314
337
  const messages = {};
315
338
  for (const [name, msg] of Object.entries(rawMessages)) {
316
- messages[name] = convertMessage(msg, isV3, defaultContentType);
339
+ messages[name] = convertMessage(msg, isV3, defaultContentType, rawComponentMessages[name]?.traits);
317
340
  }
318
341
  return {
319
342
  schemas: api.components?.schemas ?? {},
@@ -394,19 +417,17 @@ function useAsyncApiSpec(spec) {
394
417
  return useSpecFetcher(spec, parser);
395
418
  }
396
419
 
397
- function ExternalDocsLink({ externalDocs, context, className }) {
398
- const label = externalDocs.description ?? "External docs";
399
- const ariaLabel = `${context ? `${context} external docs` : "External docs"}, opens in new tab`;
420
+ function ExternalLink({ href, children, label, className }) {
400
421
  return /* @__PURE__ */ jsxs(
401
422
  "a",
402
423
  {
403
- href: externalDocs.url,
424
+ href,
404
425
  target: "_blank",
405
426
  rel: "noopener noreferrer",
406
- "aria-label": ariaLabel,
427
+ "aria-label": `${label}, opens in new tab`,
407
428
  className: cx(linkStyle$1, className),
408
429
  children: [
409
- label,
430
+ children,
410
431
  /* @__PURE__ */ jsx(Icon, { name: "external-link", size: "0.875em" })
411
432
  ]
412
433
  }
@@ -416,7 +437,6 @@ const linkStyle$1 = css({
416
437
  display: "inline-flex",
417
438
  alignItems: "center",
418
439
  gap: "0.25rem",
419
- fontSize: "var(--omnispec-font-size-xs)",
420
440
  color: "var(--omnispec-fg-link)",
421
441
  textDecoration: "none",
422
442
  minWidth: 0,
@@ -426,6 +446,15 @@ const linkStyle$1 = css({
426
446
  }
427
447
  });
428
448
 
449
+ function ExternalDocsLink({ externalDocs, context, className }) {
450
+ const label = externalDocs.description ?? "External docs";
451
+ const ariaLabel = context ? `${context} external docs` : "External docs";
452
+ return /* @__PURE__ */ jsx(ExternalLink, { href: externalDocs.url, label: ariaLabel, className: cx(docsLinkStyle, className), children: label });
453
+ }
454
+ const docsLinkStyle = css({
455
+ fontSize: "var(--omnispec-font-size-xs)"
456
+ });
457
+
429
458
  const containerStyle$4 = css({
430
459
  marginBottom: "2rem",
431
460
  paddingBottom: "1.5rem",
@@ -489,12 +518,19 @@ function AsyncOverview({ spec }) {
489
518
  ] }),
490
519
  spec.contact.url && /* @__PURE__ */ jsxs("span", { children: [
491
520
  " \xB7 ",
492
- /* @__PURE__ */ jsx("a", { href: spec.contact.url, target: "_blank", rel: "noopener noreferrer", className: linkStyle, children: spec.contact.url })
521
+ /* @__PURE__ */ jsx(
522
+ ExternalLink,
523
+ {
524
+ href: spec.contact.url,
525
+ label: `${spec.contact.name ?? spec.title ?? "Contact"} website`,
526
+ children: spec.contact.url
527
+ }
528
+ )
493
529
  ] })
494
530
  ] }),
495
531
  spec.license && /* @__PURE__ */ jsxs("div", { className: metaStyle, children: [
496
532
  "License: ",
497
- spec.license.url ? /* @__PURE__ */ jsx("a", { href: spec.license.url, target: "_blank", rel: "noopener noreferrer", className: linkStyle, children: spec.license.name }) : spec.license.name
533
+ spec.license.url ? /* @__PURE__ */ jsx(ExternalLink, { href: spec.license.url, label: `${spec.license.name} license`, children: spec.license.name }) : spec.license.name
498
534
  ] }),
499
535
  spec.externalDocs && /* @__PURE__ */ jsx("div", { className: metaStyle, children: /* @__PURE__ */ jsx(ExternalDocsLink, { externalDocs: spec.externalDocs, context: spec.title ?? "API" }) })
500
536
  ] });
@@ -574,7 +610,7 @@ function isScalarArray(value) {
574
610
  (item) => item === null || typeof item !== "object"
575
611
  );
576
612
  }
577
- function renderValue(value) {
613
+ function renderValue(value, verbatim) {
578
614
  if (value === null) return /* @__PURE__ */ jsx("span", { className: mutedStyle, children: "null" });
579
615
  if (typeof value === "boolean") return /* @__PURE__ */ jsx("code", { className: codeStyle, children: value ? "true" : "false" });
580
616
  if (typeof value === "number") return /* @__PURE__ */ jsx("code", { className: codeStyle, children: String(value) });
@@ -592,7 +628,7 @@ function renderValue(value) {
592
628
  }
593
629
  const nested = Object.entries(value).filter(([, v]) => v !== void 0);
594
630
  if (nested.length === 0) return /* @__PURE__ */ jsx("span", { className: mutedStyle, children: "none" });
595
- return /* @__PURE__ */ jsx("div", { className: nestedStyle, children: /* @__PURE__ */ jsx(FieldRows, { fields: nested }) });
631
+ return /* @__PURE__ */ jsx("div", { className: nestedStyle, children: /* @__PURE__ */ jsx(FieldRows, { fields: nested, verbatim }) });
596
632
  }
597
633
  return /* @__PURE__ */ jsx("code", { className: jsonStyle, children: JSON.stringify(value) });
598
634
  }
@@ -620,6 +656,9 @@ function SchemaLeaf({ schema }) {
620
656
  const type = typeof schema.type === "string" ? schema.type : void 0;
621
657
  const description = typeof schema.description === "string" ? schema.description : void 0;
622
658
  const enumValues = Array.isArray(schema.enum) ? schema.enum : void 0;
659
+ if (enumValues && enumValues.length === 1 && !description) {
660
+ return /* @__PURE__ */ jsx("div", { className: schemaLeafStyle, children: /* @__PURE__ */ jsx("code", { className: enumItemStyle, children: String(enumValues[0]) }) });
661
+ }
623
662
  return /* @__PURE__ */ jsxs("div", { className: schemaLeafStyle, children: [
624
663
  type && /* @__PURE__ */ jsx("span", { className: schemaTypeStyle, children: type }),
625
664
  description && /* @__PURE__ */ jsx("span", { className: schemaDescStyle, children: description }),
@@ -629,11 +668,14 @@ function SchemaLeaf({ schema }) {
629
668
  ] })
630
669
  ] });
631
670
  }
632
- function FieldRows({ fields }) {
671
+ function FieldRows({
672
+ fields,
673
+ verbatim = false
674
+ }) {
633
675
  if (fields.length === 0) return null;
634
676
  return /* @__PURE__ */ jsx("dl", { className: listStyle$1, children: fields.map(([field, value]) => /* @__PURE__ */ jsxs("div", { className: rowStyle$1, children: [
635
- /* @__PURE__ */ jsx("dt", { className: nameStyle$1, children: humanizeField(field) }),
636
- /* @__PURE__ */ jsx("dd", { className: valueStyle, children: renderValue(value) })
677
+ /* @__PURE__ */ jsx("dt", { className: nameStyle$1, children: verbatim ? field : humanizeField(field) }),
678
+ /* @__PURE__ */ jsx("dd", { className: valueStyle, children: renderValue(value, verbatim) })
637
679
  ] }, field)) });
638
680
  }
639
681
  const listStyle$1 = css({
@@ -754,7 +796,11 @@ function BindingsSection({ bindings, title = "Bindings" }) {
754
796
  );
755
797
  return /* @__PURE__ */ jsxs("div", { className: protocolCardStyle, children: [
756
798
  /* @__PURE__ */ jsx("div", { className: protocolHeaderStyle, children: /* @__PURE__ */ jsx(ProtocolBadge, { protocol }) }),
757
- rows.length > 0 ? /* @__PURE__ */ jsx(FieldRows, { fields: rows }) : /* @__PURE__ */ jsx("p", { className: emptyStyle, children: "No binding fields." })
799
+ rows.length > 0 ? (
800
+ // Binding/config keys are shown verbatim — their acronyms (URL,
801
+ // ID) and dotted names (cleanup.policy) must not be title-cased.
802
+ /* @__PURE__ */ jsx(FieldRows, { fields: rows, verbatim: true })
803
+ ) : /* @__PURE__ */ jsx("p", { className: emptyStyle, children: "No binding fields." })
758
804
  ] }, protocol);
759
805
  }) })
760
806
  ] });
@@ -792,17 +838,20 @@ function SecurityRequirementBadges({ requirements, label = "Security" }) {
792
838
  if (!requirements || requirements.length === 0) return null;
793
839
  return /* @__PURE__ */ jsxs("div", { className: rowStyle, children: [
794
840
  /* @__PURE__ */ jsx("span", { className: labelStyle, children: label }),
795
- requirements.map((alternative, altIndex) => /* @__PURE__ */ jsxs(Fragment, { children: [
796
- altIndex > 0 && /* @__PURE__ */ jsx("span", { className: orStyle, children: "OR" }),
797
- alternative.map((scheme) => /* @__PURE__ */ jsxs("a", { href: `#security-${scheme.name}`, className: badgeStyle, children: [
841
+ requirements.map((alternative, altIndex) => {
842
+ const chips = alternative.map((scheme) => /* @__PURE__ */ jsxs("a", { href: `#security-${scheme.name}`, className: badgeStyle, children: [
798
843
  /* @__PURE__ */ jsx(Icon, { name: "lock", size: "0.625rem", strokeWidth: 2.5 }),
799
844
  scheme.name,
800
845
  scheme.scopes.length > 0 && /* @__PURE__ */ jsxs("span", { className: scopesStyle$1, children: [
801
846
  /* @__PURE__ */ jsx("span", { className: scopeSepStyle, children: "\xB7" }),
802
847
  scheme.scopes.join(", ")
803
848
  ] })
804
- ] }, scheme.name))
805
- ] }, altIndex))
849
+ ] }, scheme.name));
850
+ return altIndex === 0 ? /* @__PURE__ */ jsx(Fragment, { children: chips }, altIndex) : /* @__PURE__ */ jsxs("span", { className: orGroupStyle, children: [
851
+ /* @__PURE__ */ jsx("span", { className: orStyle, children: "OR" }),
852
+ chips
853
+ ] }, altIndex);
854
+ })
806
855
  ] });
807
856
  }
808
857
  const rowStyle = css({
@@ -810,7 +859,16 @@ const rowStyle = css({
810
859
  alignItems: "center",
811
860
  gap: "0.375rem",
812
861
  flexWrap: "wrap",
813
- marginTop: "0.5rem"
862
+ marginTop: "0.5rem",
863
+ // Breathing room below the security row so it doesn't butt against the next
864
+ // section on narrow screens (finding 3).
865
+ marginBottom: "0.5rem"
866
+ });
867
+ const orGroupStyle = css({
868
+ display: "inline-flex",
869
+ alignItems: "center",
870
+ gap: "0.375rem",
871
+ whiteSpace: "nowrap"
814
872
  });
815
873
  const labelStyle = css({
816
874
  fontSize: "var(--omnispec-font-size-xxs)",
@@ -1008,6 +1066,56 @@ function ServerList({ servers }) {
1008
1066
  ] });
1009
1067
  }
1010
1068
 
1069
+ function TagChips({ tags, onTagClick }) {
1070
+ if (!tags || tags.length === 0) return null;
1071
+ return /* @__PURE__ */ jsx("div", { className: tagChipsStyle$1, children: tags.map((tag) => /* @__PURE__ */ jsxs("span", { className: tagItemStyle, children: [
1072
+ onTagClick ? /* @__PURE__ */ jsx(
1073
+ "button",
1074
+ {
1075
+ type: "button",
1076
+ className: tagChipButtonStyle$1,
1077
+ title: tag.description,
1078
+ onClick: () => onTagClick(tag.name),
1079
+ children: tag.name
1080
+ }
1081
+ ) : /* @__PURE__ */ jsx("span", { className: tagChipStyle$1, title: tag.description, children: tag.name }),
1082
+ tag.externalDocs && /* @__PURE__ */ jsx(ExternalDocsLink, { externalDocs: tag.externalDocs, context: tag.name })
1083
+ ] }, tag.name)) });
1084
+ }
1085
+ const tagChipsStyle$1 = css({
1086
+ display: "flex",
1087
+ flexWrap: "wrap",
1088
+ alignItems: "center",
1089
+ gap: "0.5rem",
1090
+ marginBottom: "0.5rem"
1091
+ });
1092
+ const tagItemStyle = css({
1093
+ display: "inline-flex",
1094
+ alignItems: "center",
1095
+ gap: "0.25rem"
1096
+ });
1097
+ const tagChipStyle$1 = css({
1098
+ fontSize: "var(--omnispec-font-size-xxs)",
1099
+ fontFamily: "var(--omnispec-font-mono)",
1100
+ color: "var(--omnispec-fg-secondary)",
1101
+ backgroundColor: "var(--omnispec-bg-tertiary)",
1102
+ padding: "1px 0.5rem",
1103
+ borderRadius: "0.625rem"
1104
+ });
1105
+ const tagChipButtonStyle$1 = css({
1106
+ fontSize: "var(--omnispec-font-size-xxs)",
1107
+ fontFamily: "var(--omnispec-font-mono)",
1108
+ color: "var(--omnispec-fg-secondary)",
1109
+ backgroundColor: "var(--omnispec-bg-tertiary)",
1110
+ padding: "1px 0.5rem",
1111
+ borderRadius: "0.625rem",
1112
+ border: "none",
1113
+ cursor: "pointer",
1114
+ "&:hover": {
1115
+ color: "var(--omnispec-fg-primary)"
1116
+ }
1117
+ });
1118
+
1011
1119
  function detectSchemaFormat(schemaFormat) {
1012
1120
  if (!schemaFormat) return "json";
1013
1121
  const f = schemaFormat.toLowerCase();
@@ -1086,7 +1194,11 @@ function primitive(name) {
1086
1194
  function MessagePayload({ message, idx }) {
1087
1195
  const rawPayload = message.payload;
1088
1196
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1089
- message.tags && message.tags.length > 0 && /* @__PURE__ */ jsx("div", { className: tagChipsStyle$2, children: message.tags.map((tag) => /* @__PURE__ */ jsx("span", { className: tagChipStyle$2, title: tag.description, children: tag.name }, tag.name)) }),
1197
+ message.tags && message.tags.length > 0 && /* @__PURE__ */ jsx(TagChips, { tags: message.tags }),
1198
+ message.appliedTraits && message.appliedTraits.length > 0 && /* @__PURE__ */ jsxs("div", { className: traitsRowStyle, children: [
1199
+ /* @__PURE__ */ jsx("span", { className: traitsLabelStyle, children: "Traits:" }),
1200
+ message.appliedTraits.map((trait, i) => /* @__PURE__ */ jsx("code", { className: traitChipStyle, children: trait }, `${trait}-${i}`))
1201
+ ] }),
1090
1202
  message.externalDocs && /* @__PURE__ */ jsx("div", { className: externalDocsStyle, children: /* @__PURE__ */ jsx(
1091
1203
  ExternalDocsLink,
1092
1204
  {
@@ -1115,10 +1227,23 @@ function PayloadBody({
1115
1227
  }
1116
1228
  const format = detectSchemaFormat(schemaFormat);
1117
1229
  if (format === "protobuf") {
1118
- return /* @__PURE__ */ jsxs("div", { children: [
1119
- /* @__PURE__ */ jsx(CodeBlock, { code: JSON.stringify(rawPayload, null, 2), language: "json" }),
1120
- /* @__PURE__ */ jsx("p", { className: proNoteStyle, children: "Full Protobuf schema rendering is available in Pro." })
1121
- ] });
1230
+ const isSource = typeof rawPayload === "string";
1231
+ const code = isSource ? rawPayload : JSON.stringify(rawPayload, null, 2);
1232
+ return /* @__PURE__ */ jsx(
1233
+ Tabs,
1234
+ {
1235
+ tabs: [
1236
+ {
1237
+ id: `schema-${idx}`,
1238
+ label: "Payload Schema",
1239
+ content: /* @__PURE__ */ jsxs("div", { children: [
1240
+ /* @__PURE__ */ jsx(CodeBlock, { code, language: isSource ? "text" : "json" }),
1241
+ /* @__PURE__ */ jsx("p", { className: proNoteStyle, children: "Full Protobuf schema rendering is available in Pro." })
1242
+ ] })
1243
+ }
1244
+ ]
1245
+ }
1246
+ );
1122
1247
  }
1123
1248
  const schema = format === "avro" ? avroToJsonSchema(rawPayload) : rawPayload;
1124
1249
  return /* @__PURE__ */ jsx(
@@ -1154,22 +1279,28 @@ function MessageExample({ message, schema }) {
1154
1279
  /* @__PURE__ */ jsx(CodeBlock, { code, language: "json" })
1155
1280
  ] });
1156
1281
  }
1157
- const tagChipsStyle$2 = css({
1282
+ const externalDocsStyle = css({
1283
+ marginBottom: "0.5rem"
1284
+ });
1285
+ const traitsRowStyle = css({
1158
1286
  display: "flex",
1287
+ alignItems: "center",
1159
1288
  flexWrap: "wrap",
1160
- gap: "0.25rem",
1289
+ gap: "0.375rem",
1161
1290
  marginBottom: "0.5rem"
1162
1291
  });
1163
- const tagChipStyle$2 = css({
1164
- fontSize: "var(--omnispec-font-size-xxs)",
1292
+ const traitsLabelStyle = css({
1293
+ fontSize: "var(--omnispec-font-size-xs)",
1294
+ fontWeight: 700,
1295
+ color: "var(--omnispec-fg-muted)"
1296
+ });
1297
+ const traitChipStyle = css({
1165
1298
  fontFamily: "var(--omnispec-font-mono)",
1299
+ fontSize: "var(--omnispec-font-size-xxs)",
1166
1300
  color: "var(--omnispec-fg-secondary)",
1167
1301
  backgroundColor: "var(--omnispec-bg-tertiary)",
1168
- padding: "1px 0.5rem",
1169
- borderRadius: "0.625rem"
1170
- });
1171
- const externalDocsStyle = css({
1172
- marginBottom: "0.5rem"
1302
+ padding: "0.0625rem 0.375rem",
1303
+ borderRadius: "var(--omnispec-border-radius)"
1173
1304
  });
1174
1305
  const schemaFormatBadgeStyle = css({
1175
1306
  display: "inline-block",
@@ -1230,6 +1361,7 @@ function ChannelDetail({ channel, id, expandAll, expandGeneration, onTagClick })
1230
1361
  /* @__PURE__ */ jsx(MethodBar, { label: primaryLabel, path: channel.address, color: primaryColor }),
1231
1362
  /* @__PURE__ */ jsxs("div", { className: bodyStyle, children: [
1232
1363
  channel.description && /* @__PURE__ */ jsx("div", { className: sectionStyle$1, children: /* @__PURE__ */ jsx(MarkdownRenderer, { content: channel.description }) }),
1364
+ channel.tags && channel.tags.length > 0 && /* @__PURE__ */ jsx(TagChips, { tags: channel.tags, onTagClick }),
1233
1365
  channel.externalDocs && /* @__PURE__ */ jsx("div", { className: sectionStyle$1, children: /* @__PURE__ */ jsx(ExternalDocsLink, { externalDocs: channel.externalDocs, context: channel.address }) }),
1234
1366
  channel.parameters && Object.keys(channel.parameters).length > 0 && /* @__PURE__ */ jsxs("div", { className: sectionStyle$1, children: [
1235
1367
  /* @__PURE__ */ jsx("h4", { className: sectionTitleStyle$1, children: "Parameters" }),
@@ -1259,32 +1391,22 @@ function ChannelDetail({ channel, id, expandAll, expandGeneration, onTagClick })
1259
1391
  /* @__PURE__ */ jsx(BindingsSection, { bindings: channel.bindings, title: "Channel Bindings" }),
1260
1392
  channel.operations.map((op, idx) => /* @__PURE__ */ jsxs("div", { id: id ? `${id}-op-${idx}` : void 0, className: operationStyle, children: [
1261
1393
  /* @__PURE__ */ jsxs("div", { className: opHeaderStyle$1, children: [
1262
- /* @__PURE__ */ jsx(
1263
- "span",
1264
- {
1265
- className: cx(actionBadgeBase$1, css({
1266
- backgroundColor: actionColors$1[op.action] ?? "var(--omnispec-fg-muted)"
1267
- })),
1268
- children: actionLabels$1[op.action] ?? op.action.toUpperCase()
1269
- }
1270
- ),
1271
- op.operationId && /* @__PURE__ */ jsx("span", { className: opIdStyle$1, children: op.operationId }),
1272
- op.title && /* @__PURE__ */ jsx("span", { className: opTitleStyle$1, children: op.title }),
1394
+ /* @__PURE__ */ jsxs("span", { className: opNameLineStyle, children: [
1395
+ /* @__PURE__ */ jsx(
1396
+ "span",
1397
+ {
1398
+ className: cx(actionBadgeBase$1, css({
1399
+ backgroundColor: actionColors$1[op.action] ?? "var(--omnispec-fg-muted)"
1400
+ })),
1401
+ children: actionLabels$1[op.action] ?? op.action.toUpperCase()
1402
+ }
1403
+ ),
1404
+ op.operationId && /* @__PURE__ */ jsx("span", { className: opIdStyle$1, children: op.operationId }),
1405
+ op.title && /* @__PURE__ */ jsx("span", { className: opTitleStyle$1, children: op.title })
1406
+ ] }),
1273
1407
  op.summary && /* @__PURE__ */ jsx("span", { className: opSummaryStyle$1, children: op.summary })
1274
1408
  ] }),
1275
- op.tags && op.tags.length > 0 && /* @__PURE__ */ jsx("div", { className: tagChipsStyle$1, children: op.tags.map(
1276
- (tag) => onTagClick ? /* @__PURE__ */ jsx(
1277
- "button",
1278
- {
1279
- type: "button",
1280
- className: tagChipButtonStyle$1,
1281
- title: tag.description,
1282
- onClick: () => onTagClick(tag.name),
1283
- children: tag.name
1284
- },
1285
- tag.name
1286
- ) : /* @__PURE__ */ jsx("span", { className: tagChipStyle$1, title: tag.description, children: tag.name }, tag.name)
1287
- ) }),
1409
+ op.tags && op.tags.length > 0 && /* @__PURE__ */ jsx(TagChips, { tags: op.tags, onTagClick }),
1288
1410
  op.description && /* @__PURE__ */ jsx("div", { className: sectionStyle$1, children: /* @__PURE__ */ jsx(MarkdownRenderer, { content: op.description }) }),
1289
1411
  op.externalDocs && /* @__PURE__ */ jsx("div", { className: opExternalDocsStyle, children: /* @__PURE__ */ jsx(
1290
1412
  ExternalDocsLink,
@@ -1301,8 +1423,9 @@ function ChannelDetail({ channel, id, expandAll, expandGeneration, onTagClick })
1301
1423
  /* @__PURE__ */ jsx("span", { className: replyMetaLabelStyle, children: "channel" }),
1302
1424
  /* @__PURE__ */ jsx("code", { className: replyMetaValueStyle, children: op.reply.channelName })
1303
1425
  ] }),
1304
- op.reply.address && (op.reply.address.location || op.reply.address.description) && /* @__PURE__ */ jsxs("div", { className: replyMetaRowStyle, children: [
1426
+ op.reply.address && (op.reply.address.value || op.reply.address.location || op.reply.address.description) && /* @__PURE__ */ jsxs("div", { className: replyMetaRowStyle, children: [
1305
1427
  /* @__PURE__ */ jsx("span", { className: replyMetaLabelStyle, children: "address" }),
1428
+ op.reply.address.value && /* @__PURE__ */ jsx("code", { className: replyMetaValueStyle, children: op.reply.address.value }),
1306
1429
  op.reply.address.location && /* @__PURE__ */ jsx("code", { className: replyMetaValueStyle, children: op.reply.address.location }),
1307
1430
  op.reply.address.description && /* @__PURE__ */ jsx("span", { className: replyMetaDescStyle, children: op.reply.address.description })
1308
1431
  ] }),
@@ -1356,10 +1479,10 @@ function MessageView({ message, idx }) {
1356
1479
  message.summary && /* @__PURE__ */ jsx("p", { className: messageSummaryStyle$1, children: message.summary }),
1357
1480
  message.contentType && /* @__PURE__ */ jsx("code", { className: contentTypeStyle$2, children: message.contentType }),
1358
1481
  /* @__PURE__ */ jsx(MessagePayload, { message, idx }),
1359
- message.correlationId && /* @__PURE__ */ jsxs("div", { className: correlationIdStyle, children: [
1360
- /* @__PURE__ */ jsx("span", { className: correlationIdLabelStyle, children: "correlationId" }),
1361
- /* @__PURE__ */ jsx("code", { className: correlationIdLocationStyle, children: message.correlationId.location }),
1362
- message.correlationId.description && /* @__PURE__ */ jsx("span", { className: correlationIdDescStyle, children: message.correlationId.description })
1482
+ message.correlationId && /* @__PURE__ */ jsxs("div", { className: correlationIdStyle$1, children: [
1483
+ /* @__PURE__ */ jsx("span", { className: correlationIdLabelStyle$1, children: "correlationId" }),
1484
+ /* @__PURE__ */ jsx("code", { className: correlationIdLocationStyle$1, children: message.correlationId.location }),
1485
+ message.correlationId.description && /* @__PURE__ */ jsx("span", { className: correlationIdDescStyle$1, children: message.correlationId.description })
1363
1486
  ] }),
1364
1487
  message.headers && /* @__PURE__ */ jsxs("div", { className: headersWrapStyle$1, children: [
1365
1488
  /* @__PURE__ */ jsx("h4", { className: sectionTitleStyle$1, children: "Headers" }),
@@ -1426,9 +1549,16 @@ const operationStyle = css({
1426
1549
  const opHeaderStyle$1 = css({
1427
1550
  display: "flex",
1428
1551
  alignItems: "center",
1429
- gap: "0.5rem",
1552
+ flexWrap: "wrap",
1553
+ gap: "0.25rem 0.5rem",
1430
1554
  marginBottom: "0.5rem"
1431
1555
  });
1556
+ const opNameLineStyle = css({
1557
+ display: "inline-flex",
1558
+ alignItems: "center",
1559
+ flexWrap: "wrap",
1560
+ gap: "0.5rem"
1561
+ });
1432
1562
  const actionBadgeBase$1 = css({
1433
1563
  display: "inline-block",
1434
1564
  padding: "0.125rem 0.375rem",
@@ -1451,6 +1581,7 @@ const opTitleStyle$1 = css({
1451
1581
  color: "var(--omnispec-fg-primary)"
1452
1582
  });
1453
1583
  const opSummaryStyle$1 = css({
1584
+ flexBasis: "100%",
1454
1585
  fontSize: "var(--omnispec-font-size-sm)",
1455
1586
  color: "var(--omnispec-fg-secondary)"
1456
1587
  });
@@ -1506,20 +1637,20 @@ const messageCountStyle = css({
1506
1637
  fontSize: "var(--omnispec-font-size-xs)",
1507
1638
  color: "var(--omnispec-fg-muted)"
1508
1639
  });
1509
- const correlationIdStyle = css({
1640
+ const correlationIdStyle$1 = css({
1510
1641
  display: "flex",
1511
1642
  alignItems: "center",
1512
1643
  gap: "0.5rem",
1513
1644
  flexWrap: "wrap",
1514
1645
  marginTop: "0.75rem"
1515
1646
  });
1516
- const correlationIdLabelStyle = css({
1647
+ const correlationIdLabelStyle$1 = css({
1517
1648
  fontSize: "var(--omnispec-font-size-xs)",
1518
1649
  fontWeight: 700,
1519
1650
  color: "var(--omnispec-fg-primary)",
1520
1651
  fontFamily: "var(--omnispec-font-mono)"
1521
1652
  });
1522
- const correlationIdLocationStyle = css({
1653
+ const correlationIdLocationStyle$1 = css({
1523
1654
  fontFamily: "var(--omnispec-font-mono)",
1524
1655
  fontSize: "var(--omnispec-font-size-xs)",
1525
1656
  color: "var(--omnispec-fg-code)",
@@ -1527,7 +1658,7 @@ const correlationIdLocationStyle = css({
1527
1658
  padding: "1px 0.375rem",
1528
1659
  borderRadius: "0.1875rem"
1529
1660
  });
1530
- const correlationIdDescStyle = css({
1661
+ const correlationIdDescStyle$1 = css({
1531
1662
  fontSize: "var(--omnispec-font-size-xs)",
1532
1663
  color: "var(--omnispec-fg-secondary)"
1533
1664
  });
@@ -1568,33 +1699,6 @@ const replyMetaDescStyle = css({
1568
1699
  fontSize: "var(--omnispec-font-size-xs)",
1569
1700
  color: "var(--omnispec-fg-secondary)"
1570
1701
  });
1571
- const tagChipsStyle$1 = css({
1572
- display: "flex",
1573
- flexWrap: "wrap",
1574
- gap: "0.25rem",
1575
- marginBottom: "0.5rem"
1576
- });
1577
- const tagChipStyle$1 = css({
1578
- fontSize: "var(--omnispec-font-size-xxs)",
1579
- fontFamily: "var(--omnispec-font-mono)",
1580
- color: "var(--omnispec-fg-secondary)",
1581
- backgroundColor: "var(--omnispec-bg-tertiary)",
1582
- padding: "1px 0.5rem",
1583
- borderRadius: "0.625rem"
1584
- });
1585
- const tagChipButtonStyle$1 = css({
1586
- fontSize: "var(--omnispec-font-size-xxs)",
1587
- fontFamily: "var(--omnispec-font-mono)",
1588
- color: "var(--omnispec-fg-secondary)",
1589
- backgroundColor: "var(--omnispec-bg-tertiary)",
1590
- padding: "1px 0.5rem",
1591
- borderRadius: "0.625rem",
1592
- border: "none",
1593
- cursor: "pointer",
1594
- "&:hover": {
1595
- color: "var(--omnispec-fg-primary)"
1596
- }
1597
- });
1598
1702
 
1599
1703
  const actionLabels = {
1600
1704
  publish: "PUB",
@@ -1900,11 +2004,13 @@ function ComponentsSection({ components }) {
1900
2004
  children: [
1901
2005
  message.title && /* @__PURE__ */ jsx("p", { className: messageSummaryStyle, children: message.title }),
1902
2006
  message.summary && /* @__PURE__ */ jsx("p", { className: messageSummaryStyle, children: message.summary }),
1903
- message.contentType && /* @__PURE__ */ jsxs("code", { className: contentTypeStyle, children: [
1904
- message.contentType,
1905
- message.contentTypeInherited && /* @__PURE__ */ jsx("span", { className: contentTypeHintStyle, children: " (default)" })
1906
- ] }),
2007
+ message.contentType && /* @__PURE__ */ jsx("code", { className: contentTypeStyle, children: message.contentType }),
1907
2008
  /* @__PURE__ */ jsx(MessagePayload, { message, idx: `message-${name}` }),
2009
+ message.correlationId && /* @__PURE__ */ jsxs("div", { className: correlationIdStyle, children: [
2010
+ /* @__PURE__ */ jsx("span", { className: correlationIdLabelStyle, children: "correlationId" }),
2011
+ /* @__PURE__ */ jsx("code", { className: correlationIdLocationStyle, children: message.correlationId.location }),
2012
+ message.correlationId.description && /* @__PURE__ */ jsx("span", { className: correlationIdDescStyle, children: message.correlationId.description })
2013
+ ] }),
1908
2014
  message.headers && /* @__PURE__ */ jsxs("div", { className: headersWrapStyle, children: [
1909
2015
  /* @__PURE__ */ jsx("h4", { className: headersTitleStyle, children: "Headers" }),
1910
2016
  /* @__PURE__ */ jsx(SchemaTree, { nodes: schemaToNodes(message.headers) })
@@ -1973,9 +2079,30 @@ const contentTypeStyle = css({
1973
2079
  color: "var(--omnispec-fg-muted)",
1974
2080
  marginBottom: "0.5rem"
1975
2081
  });
1976
- const contentTypeHintStyle = css({
1977
- fontStyle: "italic",
1978
- color: "var(--omnispec-fg-muted)"
2082
+ const correlationIdStyle = css({
2083
+ display: "flex",
2084
+ alignItems: "center",
2085
+ gap: "0.5rem",
2086
+ flexWrap: "wrap",
2087
+ marginTop: "0.75rem"
2088
+ });
2089
+ const correlationIdLabelStyle = css({
2090
+ fontSize: "var(--omnispec-font-size-xs)",
2091
+ fontWeight: 700,
2092
+ color: "var(--omnispec-fg-primary)",
2093
+ fontFamily: "var(--omnispec-font-mono)"
2094
+ });
2095
+ const correlationIdLocationStyle = css({
2096
+ fontFamily: "var(--omnispec-font-mono)",
2097
+ fontSize: "var(--omnispec-font-size-xs)",
2098
+ color: "var(--omnispec-fg-code)",
2099
+ backgroundColor: "var(--omnispec-bg-tertiary)",
2100
+ padding: "1px 0.375rem",
2101
+ borderRadius: "0.1875rem"
2102
+ });
2103
+ const correlationIdDescStyle = css({
2104
+ fontSize: "var(--omnispec-font-size-xs)",
2105
+ color: "var(--omnispec-fg-secondary)"
1979
2106
  });
1980
2107
  const headersWrapStyle = css({
1981
2108
  marginTop: "1rem"
@@ -2148,7 +2275,7 @@ function channelTagNames(channel) {
2148
2275
  function hasAnyTags(channels) {
2149
2276
  return channels.some((ch) => channelTagNames(ch).length > 0);
2150
2277
  }
2151
- function groupChannelsByTag(channels, specTags) {
2278
+ function groupChannelsByTag(channels, specTags, xTagGroups) {
2152
2279
  const byName = /* @__PURE__ */ new Map();
2153
2280
  const untagged = [];
2154
2281
  for (const channel of channels) {
@@ -2163,9 +2290,13 @@ function groupChannelsByTag(channels, specTags) {
2163
2290
  byName.set(name, list);
2164
2291
  }
2165
2292
  }
2293
+ const xGrouped = (xTagGroups ?? []).flatMap((xg) => xg.tags).filter((name) => byName.has(name));
2166
2294
  const declared = (specTags ?? []).map((t) => t.name);
2167
- const operationOnly = Array.from(byName.keys()).filter((name) => !declared.includes(name)).sort();
2168
- const orderedNames = [...declared.filter((name) => byName.has(name)), ...operationOnly];
2295
+ const seen = new Set(xGrouped);
2296
+ const declaredRemaining = declared.filter((name) => byName.has(name) && !seen.has(name));
2297
+ declaredRemaining.forEach((name) => seen.add(name));
2298
+ const operationOnly = Array.from(byName.keys()).filter((name) => !seen.has(name)).sort();
2299
+ const orderedNames = [.../* @__PURE__ */ new Set([...xGrouped, ...declaredRemaining, ...operationOnly])];
2169
2300
  const tagMetaByName = new Map((specTags ?? []).map((t) => [t.name, t]));
2170
2301
  const usedIds = /* @__PURE__ */ new Set();
2171
2302
  const uniqueId = (base) => {
@@ -2252,10 +2383,26 @@ function AsyncApiSpec({
2252
2383
  const [allExpanded, setAllExpanded] = useState(defaultExpandOperations);
2253
2384
  const [expandGeneration, setExpandGeneration] = useState(0);
2254
2385
  const [navGrouping, setNavGrouping] = useState("channel");
2386
+ const [activeTagFilter, setActiveTagFilter] = useState(null);
2255
2387
  const handleToggleExpand = useCallback(() => {
2256
2388
  setAllExpanded((prev) => !prev);
2257
2389
  setExpandGeneration((prev) => prev + 1);
2258
2390
  }, []);
2391
+ const handleTagFilter = useCallback((tagName) => {
2392
+ setActiveTagFilter((prev) => {
2393
+ const next = prev === tagName ? null : tagName;
2394
+ setSearchQuery(next ?? "");
2395
+ return next;
2396
+ });
2397
+ }, []);
2398
+ const handleSearch = useCallback((query) => {
2399
+ setSearchQuery(query);
2400
+ setActiveTagFilter(null);
2401
+ }, []);
2402
+ const clearTagFilter = useCallback(() => {
2403
+ setActiveTagFilter(null);
2404
+ setSearchQuery("");
2405
+ }, []);
2259
2406
  useMemo(() => {
2260
2407
  if (parsedSpec && onSpecLoaded) {
2261
2408
  const major = parseInt(parsedSpec.specVersion.split(".")[0], 10);
@@ -2277,7 +2424,7 @@ function AsyncApiSpec({
2277
2424
  }, [parsedSpec, searchQuery]);
2278
2425
  const grouped = useMemo(() => hasAnyTags(filteredChannels), [filteredChannels]);
2279
2426
  const tagGroups = useMemo(
2280
- () => grouped ? groupChannelsByTag(filteredChannels, parsedSpec?.tags) : [],
2427
+ () => grouped ? groupChannelsByTag(filteredChannels, parsedSpec?.tags, parsedSpec?.tagGroups) : [],
2281
2428
  [grouped, filteredChannels, parsedSpec]
2282
2429
  );
2283
2430
  const channelDomId = useMemo(() => {
@@ -2309,6 +2456,10 @@ function AsyncApiSpec({
2309
2456
  () => filteredChannels.some((ch) => ch.operations.length > 0),
2310
2457
  [filteredChannels]
2311
2458
  );
2459
+ const unassignedOpCount = useMemo(
2460
+ () => parsedSpec?.channels.filter((ch) => ch.name === "__unassigned__").reduce((sum, ch) => sum + ch.operations.length, 0) ?? 0,
2461
+ [parsedSpec]
2462
+ );
2312
2463
  const navItems = useMemo(() => {
2313
2464
  if (!parsedSpec) return [];
2314
2465
  const actionLabel = (action) => action.toUpperCase();
@@ -2411,9 +2562,27 @@ function AsyncApiSpec({
2411
2562
  SearchBar,
2412
2563
  {
2413
2564
  placeholder: "Filter channels...",
2414
- onSearch: setSearchQuery
2565
+ onSearch: handleSearch,
2566
+ value: searchQuery
2415
2567
  }
2416
2568
  ),
2569
+ activeTagFilter && /* @__PURE__ */ jsxs("div", { className: activeFilterStyle, children: [
2570
+ /* @__PURE__ */ jsx("span", { className: activeFilterLabelStyle, children: "Filtered by tag:" }),
2571
+ /* @__PURE__ */ jsx("span", { className: activeFilterTagStyle, children: activeTagFilter }),
2572
+ /* @__PURE__ */ jsxs(
2573
+ "button",
2574
+ {
2575
+ type: "button",
2576
+ className: activeFilterClearStyle,
2577
+ onClick: clearTagFilter,
2578
+ "aria-label": `Clear tag filter: ${activeTagFilter}`,
2579
+ children: [
2580
+ "Clear",
2581
+ /* @__PURE__ */ jsx(Icon, { name: "xmark", size: "0.75rem", strokeWidth: 2.5 })
2582
+ ]
2583
+ }
2584
+ )
2585
+ ] }),
2417
2586
  hasOperations && /* @__PURE__ */ jsxs("div", { className: groupToggleStyle, role: "group", "aria-label": "Sidebar grouping", children: [
2418
2587
  /* @__PURE__ */ jsx("span", { className: groupToggleLabelStyle, children: "Group by" }),
2419
2588
  /* @__PURE__ */ jsxs("div", { className: groupToggleButtonsStyle, children: [
@@ -2476,6 +2645,7 @@ function AsyncApiSpec({
2476
2645
  /* @__PURE__ */ jsx("div", { id: "servers", children: /* @__PURE__ */ jsx(ServerList, { servers: parsedSpec.servers }) }),
2477
2646
  /* @__PURE__ */ jsxs("div", { id: "channels", children: [
2478
2647
  /* @__PURE__ */ jsx("h2", { className: css({ margin: "0 0 1rem", fontSize: "var(--omnispec-h2-font-size)", fontWeight: "var(--omnispec-h2-font-weight)", color: "var(--omnispec-h2-color)" }), children: "Channels" }),
2648
+ unassignedOpCount > 0 && /* @__PURE__ */ 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.` }),
2479
2649
  filteredChannels.length === 0 ? /* @__PURE__ */ 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__ */ jsxs("section", { id: `taggroup-${group.id}`, className: tagSectionStyle, children: [
2480
2650
  /* @__PURE__ */ jsx(TagGroupHeader, { group }),
2481
2651
  group.channels.map((channel) => /* @__PURE__ */ jsx(
@@ -2486,7 +2656,7 @@ function AsyncApiSpec({
2486
2656
  displayMode: resolvedDisplayMode,
2487
2657
  expandAll: allExpanded,
2488
2658
  expandGeneration,
2489
- onTagClick: setSearchQuery
2659
+ onTagClick: handleTagFilter
2490
2660
  },
2491
2661
  `${group.id}-${channel.name}`
2492
2662
  ))
@@ -2498,7 +2668,7 @@ function AsyncApiSpec({
2498
2668
  displayMode: resolvedDisplayMode,
2499
2669
  expandAll: allExpanded,
2500
2670
  expandGeneration,
2501
- onTagClick: setSearchQuery
2671
+ onTagClick: handleTagFilter
2502
2672
  },
2503
2673
  channel.name
2504
2674
  ))
@@ -2551,6 +2721,17 @@ const emptyStateStyle = css({
2551
2721
  color: "var(--omnispec-fg-muted)",
2552
2722
  fontStyle: "italic"
2553
2723
  });
2724
+ const unassignedNoticeStyle = css({
2725
+ display: "flex",
2726
+ alignItems: "center",
2727
+ gap: "0.5rem",
2728
+ padding: "0.625rem 0.875rem",
2729
+ marginBottom: "1rem",
2730
+ borderRadius: "var(--omnispec-border-radius)",
2731
+ backgroundColor: "color-mix(in srgb, var(--omnispec-color-warning) 12%, transparent)",
2732
+ color: "var(--omnispec-color-warning)",
2733
+ fontSize: "var(--omnispec-font-size-sm)"
2734
+ });
2554
2735
  const noResultsNavStyle = css({
2555
2736
  fontStyle: "italic",
2556
2737
  color: "var(--omnispec-fg-muted)",
@@ -2594,6 +2775,48 @@ const groupToggleActiveStyle = css({
2594
2775
  const tagSectionStyle = css({
2595
2776
  marginBottom: "2rem"
2596
2777
  });
2778
+ const activeFilterStyle = css({
2779
+ display: "flex",
2780
+ alignItems: "center",
2781
+ flexWrap: "wrap",
2782
+ gap: "0.375rem",
2783
+ padding: "0 0.75rem 0.5rem"
2784
+ });
2785
+ const activeFilterLabelStyle = css({
2786
+ fontSize: "var(--omnispec-font-size-xxs)",
2787
+ fontWeight: 600,
2788
+ textTransform: "uppercase",
2789
+ letterSpacing: "0.05em",
2790
+ color: "var(--omnispec-fg-muted)"
2791
+ });
2792
+ const activeFilterTagStyle = css({
2793
+ fontFamily: "var(--omnispec-font-mono)",
2794
+ fontSize: "var(--omnispec-font-size-xs)",
2795
+ fontWeight: 500,
2796
+ color: "var(--omnispec-fg-primary)",
2797
+ backgroundColor: "var(--omnispec-bg-tertiary)",
2798
+ padding: "0.125rem 0.5rem",
2799
+ borderRadius: "var(--omnispec-border-radius)"
2800
+ });
2801
+ const activeFilterClearStyle = css({
2802
+ display: "inline-flex",
2803
+ alignItems: "center",
2804
+ gap: "0.1875rem",
2805
+ border: "none",
2806
+ background: "transparent",
2807
+ cursor: "pointer",
2808
+ marginLeft: "auto",
2809
+ padding: "0.125rem 0.25rem",
2810
+ borderRadius: "var(--omnispec-border-radius)",
2811
+ fontSize: "var(--omnispec-font-size-xxs)",
2812
+ fontWeight: 600,
2813
+ textTransform: "uppercase",
2814
+ letterSpacing: "0.05em",
2815
+ color: "var(--omnispec-color-primary)",
2816
+ "&:hover": {
2817
+ backgroundColor: "var(--omnispec-bg-tertiary)"
2818
+ }
2819
+ });
2597
2820
 
2598
2821
  export { AsyncApiSpec };
2599
- //# sourceMappingURL=AsyncApiSpec-B757IIHz.js.map
2822
+ //# sourceMappingURL=AsyncApiSpec-C0tdvyEP.js.map